Skip to content

fix(tmux): restrict 'esc to interrupt' busy detection to status bar lines#451

Open
nlenepveu wants to merge 1 commit intoasheshgoplani:mainfrom
blackfuel-ai:fix/esc-to-interrupt-false-positive-in-prose
Open

fix(tmux): restrict 'esc to interrupt' busy detection to status bar lines#451
nlenepveu wants to merge 1 commit intoasheshgoplani:mainfrom
blackfuel-ai:fix/esc-to-interrupt-false-positive-in-prose

Conversation

@nlenepveu
Copy link
Copy Markdown
Contributor

@nlenepveu nlenepveu commented Mar 28, 2026

Problem

Sessions were incorrectly detected as running (busy) when their pane output contained the phrase esc to interrupt anywhere in the body text — not just in Claude Code's actual status bar.

A concrete example: a conductor session that had finished responding and was waiting at the prompt showed as running because its own output included a line like:

● I is processing (esc to interrupt = running). Verified.

This caused the heartbeat to skip the conductor (only sends when status is idle or waiting), so heartbeats silently stopped firing.

Root Cause

hasBusyIndicatorResolved checked for "esc to interrupt" across the last 25 lines of pane content, so prose far above the status bar could trigger the pattern.

Fix

Claude Code's status bar always lives below the last horizontal separator (────). The layout is:

────────────────────────  (first separator)
❯                         (prompt area)
────────────────────────  (last separator)
  ⏵⏵ bypass permissions on · esc to interrupt · ctrl+t to show tasks   ← status bar

Two new helpers scope the check to the right region:

  • isHorizontalSeparator(line) — matches lines made entirely of U+2500 box-drawing dashes (), 8+ runes wide. ASCII hyphens are intentionally excluded (they appear in prose/markdown).
  • linesAfterLastSeparator(content) — finds the last separator and returns everything below it. If the separator is the final line (idle pane), returns lines between the last two separators. Falls back to lastNLines(content, 3) when no separator is present.

The hasInterruptBusyContext call now receives linesAfterLastSeparator(content) instead of lastNLines(content, 3), making the boundary structural rather than a fixed line count. This is robust even when the status bar wraps onto 2–3 lines on narrow panes.

Tests

  • TestEscToInterruptOnlyMatchedInStatusBar — 5 cases covering real status bar variants (busy) and conductor prose output (not busy)
  • TestLinesAfterLastSeparator — 4 unit tests for the new helper: standard layout, separator-as-last-line, no separator fallback, trailing blank line stripping
  • TestEscToInterruptSeparatorBased — 2 end-to-end cases using the exact pane layout reported in the bug, verified via hasBusyIndicator

…ines

Sessions with prose output containing 'esc to interrupt' (e.g. a conductor
reporting 'I is processing (esc to interrupt = running)') were incorrectly
detected as busy/running. The interrupt phrase always appears in the last
1-2 lines of the pane (the Claude Code status bar), never in body output.

Limit the interrupt-pattern context check to the last 3 lines of the pane
instead of the full 25-line window, preventing false positives from
model-generated text that happens to contain the phrase.

Adds 5 regression tests: 3 true positives (real status bar variants) and
2 false positives (conductor prose output with parenthesised phrase).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant