Skip to content

fix(launch): stop the --print helper from clearing the pane badge - #120

Merged
NagyVikt merged 1 commit into
mainfrom
fix/tmux-badge-clobber
Aug 5, 2026
Merged

fix(launch): stop the --print helper from clearing the pane badge#120
NagyVikt merged 1 commit into
mainfrom
fix/tmux-badge-clobber

Conversation

@NagyVikt

@NagyVikt NagyVikt commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What

announceTmuxProfile() gated only on process.env.TMUX, so every cue launch reached it — including the short-lived --print skill-selector cue spawns on each session start. That helper inherits TMUX_PANE from the session it is helping, so it wrote @cue_* onto that pane and then, seconds later, its process.on("exit") handler unset all eight.

The interactive session kept running with its badge wiped off the pane border: the profile name and icon simply vanished mid-session.

Evidence

Measured on a live machine: 1 of 12 panes still had @cue_profile_name set.

%22  cmd=bun  prof=coolify  icons=🧊
%9   cmd=bun  prof=         icons=
%12  cmd=bun  prof=         icons=
%21  cmd=bun  prof=         icons=
...

The helper is the one cue spawns itself — observed live as
bun cue/src/index.ts launch claude --print --strict-mcp-config --model haiku -p "You are choosing which skills to load…",
matching classifierSpawnArgs() exactly.

Fix — two guards, for two different cases

ownsPaneBadge() adds a TTY requirement. Verified against live processes: all seven interactive launchers write to /dev/pts/N, while claude-classifier.ts spawns with stdio: ["ignore", "pipe", "ignore"]. A pipe is precisely what separates a helper run from the session the user is looking at.

The exit sweep now checks ownership. It reads the eight options back and unsets only those still holding the values it wrote. This is not redundant with the TTY guard: a nested launch — supported since #119 — has a TTY too, so the guard alone would not stop it from clearing an outer session's badge when it exits.

The read-back is one extra spawn, batched through display-message -p with a \x1f separator, in keeping with the existing set/unset batching. A failed read-back leaves the options in place — leaking a stale badge is recoverable on the next launch, clearing a live one is not.

Tests

ownsPaneBadge is extracted as a pure predicate so the rule is explicit and covered: interactive-in-tmux owns it; piped does not; outside tmux nobody does; CUE_TMUX_TITLE=0 still opts out.

Verification

Check Result
tsc --noEmit Pass (exit 0)
biome lint Pass — both files clean
New tests 4/4 pass
Full suite 28 failures, identical by name to main — zero new failures

The suite is red at baseline on main; the failing set was captured on both sides and diffed after stripping bun's per-test timing annotations.

🤖 Generated with Claude Code

announceTmuxProfile() gated only on being inside tmux, so every cue launch
reached it — including the short-lived `--print` skill-selector cue spawns on
each session start. That helper inherits TMUX_PANE from the session it is
helping, so it set @cue_* on that pane and then, seconds later, its exit
handler unset all eight. The interactive session kept running with its badge
wiped from the pane border: the profile name and icon simply vanished.

Measured on this machine: 1 of 12 live panes still had @cue_profile_name set.

Two guards, because they cover different cases:

  - ownsPaneBadge() adds a TTY requirement. Verified against live processes:
    all seven interactive launchers write to /dev/pts/N, while the classifier
    is spawned with stdio ["ignore", "pipe", "ignore"], so a pipe is exactly
    what distinguishes a helper from the session the user is looking at.

  - The exit sweep now reads the options back and unsets only those still
    holding the values it wrote. A nested launch — supported since #119 — has
    a TTY too, so the TTY guard alone would not stop it from clearing an outer
    session's badge on exit. One extra spawn, batched like the set path.

A failed read-back leaves the options in place: leaking a stale badge is
recoverable on the next launch, clearing a live one is not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@NagyVikt
NagyVikt merged commit eb130e1 into main Aug 5, 2026
6 checks passed
@NagyVikt
NagyVikt deleted the fix/tmux-badge-clobber branch August 5, 2026 12:26
NagyVikt pushed a commit that referenced this pull request Aug 7, 2026
Brings 36 commits of main onto the branch, including the pane-badge fixes
(#111, #120) the branch predates. Six conflicts, all resolved to main's side:
main's SkillSpector (#108) is a strict superset of the branch's earlier take —
same exports plus baselineDirs/resolveBaselineFor and a --baseline runner arg —
and its security.ts keeps both `security scan` and `security baseline`.
README and the smart-loader perf test take main's wording likewise.

Full suite vs both parents, same conditions: 29 failures on the merge, 28 on
main, 34 on the branch, and zero that fail on the merge but on neither parent.
Nine smart-loader tests that were red on the branch go green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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