Skip to content

fix(bin): retire the status companion once its pane stops running Codex - #267

Merged
Amplify-Logic merged 7 commits into
mainfrom
fm/status-companion-stale
Oct 6, 2026
Merged

Amplify-Logic merged 7 commits into
mainfrom
fm/status-companion-stale

Conversation

@Amplify-Logic

@Amplify-Logic Amplify-Logic commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

Intent

"Can we run a builder to remove the bottom gpt-6-astra bar? We don't need that anymore. It's just taking up screen real estate."

Context: the primary Firstmate session in herdr pane wS:p1 was earlier launched as an Astra (gpt-6-astra) primary, which split off a status companion pane running bin/fm-status-bar.sh --adapter codex --model gpt-6-astra --effort high --follow-pane wS:p1 --follow-backend herdr. The primary in that same pane is now Claude (Opus 5.5), but the companion kept running and kept showing the stale Astra bar below it, because it only exits when its followed pane is gone and the pane id was reused. Firstmate closed that pane by hand on 2026-10-02; the ask is that the bar does not come back.

What Changed

  • bin/fm-status-bar.sh: a Codex status companion now exits when the pane it follows stops running Codex, not only when that pane closes. On tmux and herdr it looks for Codex in the pane's shell and up to five levels of child processes. On herdr the lookup starts from shell_pid, so a Codex suspended with Ctrl-Z keeps its bar. Retirement only becomes possible after Codex has been seen at least once, and then Codex has to be missing for three refreshes in a row. A provider that can't report pane processes keeps the old pane-liveness behavior. Before exiting on herdr, the companion releases its zoom on the followed pane, but only if it owns that zoom.
  • tests/fm-status-bar.test.sh and tests/lib.sh: new cases for runtime-change retirement on both providers, a suspended Codex keeping its bar, and the zoom release applying only to a zoom the companion owns. The fake tmux now counts only pane-id lookups and refuses every other query, so the new process lookups don't use up a test's refresh count.
  • docs/status-bar.md: documents the retirement rule, what counts as Codex behind the pane, the herdr measurement showing the provider closes the companion pane when it exits, the zoom release on retirement, and the extra test coverage.

🤖 Generated with Claude Code

Risk Assessment

✅ Low: The change is small and bounded. Retirement only applies to the codex adapter. It arms only after Codex has been positively seen behind the pane, and it needs three consecutive misses before it fires. A missing or failing process read cannot arm it, so the pre-existing pane-liveness behavior stays the fallback. I traced the intent's scenario: an Astra companion is launched by bin/fm-primary.sh, which execs codex, and Claude later takes over the same pane. After three misses the companion exits, and since it was exec'd as the companion pane's only process, the provider closes that pane. The suspended-Codex, unarmed-launch, and zoom-ownership paths behave as the user decided in earlier rounds.

Testing

I ran the targeted tests/fm-status-bar.test.sh, which passed. I then drove the real renderer, with production-shaped launcher arguments, against a real Codex and then a real Claude in two throwaway labs: a private fm-lab tmux socket and an fm-lab-* herdr 0.7.4 session. In both, the stale gpt-6-astra companion disappeared about 3–4 seconds after Codex was replaced by Claude in the same pane, and no Astra row was left. It never retired while Codex was running, while Codex was suspended with Ctrl-Z, or before Codex had started. On herdr the launcher's zoom was off after retirement. I can't show live that the renderer, rather than herdr itself, released that zoom: herdr clears a tab's zoom whenever a pane in that tab closes. The unit suite's herdr call log is the evidence for which component sends the release. Evidence is text captures of the terminal panes and process trees. No pixel screenshots were taken, because herdr's view helper needs pyte for a text render; the panes' own text reads show the user-visible row directly. Both labs were torn down and the worktree is clean.

  • Live validation: ✅ go - 6 of 7 scenarios driven live against the product
Scenario Result Live Evidence
herdr: the captain swaps the Astra/Codex primary for Claude in the same pane, and the stale gpt-6-astra bar disappears without coming back ✅ pass live herdr-04-retire-after-claude.txt: companion pane w1:p2 gone at t+4s; w1:p1 runs claude.exe; no pane shows gpt-6-astra
tmux: the captain swaps Codex for Claude in the same pane, the companion retires, and Claude gets the full pane height ✅ pass live tmux-05-retire-after-claude.txt: %1 gone at t+3s; %0 claude.exe h=30; 0 gpt-6-astra lines
A Codex primary that is running keeps its bar (no false retirement) ✅ pass live tmux-02/03 (bar kept for over 20s with Codex running); herdr-02 (bar rendered with Codex in the foreground)
Adversarial: Codex suspended with Ctrl-Z for more than 3 refreshes keeps its bar (herdr foreground group is only the shell) ✅ pass live herdr-03-codex-suspended.txt (fg=[zsh], Codex state T, w1:p2 still rendering after 11s); tmux-04-codex-suspended.txt (bar kept after 10s)
Adversarial: a companion started before Codex has exec'd (launch window, retirement not armed) is not retired ✅ pass live tmux-01-before-codex.txt and herdr-01-before-codex.txt: companion pane still present 6s after start with only a shell in the primary pane
herdr: the launcher's zoom on the primary is off after the companion retires, so later splits are not hidden ✅ pass live herdr-04-retire-after-claude.txt: zoomed=true until t+3s, zoomed=false from t+4s once w1:p2 retired; the herdr call log in tests/fm-status-bar.test.sh shows the renderer itself sends pane zoom --off
Adversarial: a retiring companion that does not own the zoom (no --chrome-zoomed) never sends a zoom release ⏸️ untested no Can't be observed live: herdr 0.7.4 clears a tab's zoom itself whenever a pane in that tab closes, so the renderer's own call can't be told apart from the resulting zoom state. Proving it live would n…
Evidence: Targeted status-bar test log

Source: Targeted status-bar test log

ok - status bar: canonical field order and fleet projection are stable
ok - status bar: the headline is live agents and stopped records carry their own figure
ok - status bar: a task record is never counted as a running worker
ok - status bar: validating work is a separate field from a busy worker
ok - status bar: an unread fleet shows placeholders and a genuine zero still shows zero
ok - status bar: threshold colors and unavailable-metric placeholders are canonical
ok - status bar: missing and stale supervision are loud NO-WATCH alerts
ok - status bar: Claude payload normalization is guarded to a primary launch
ok - status bar: tmux companion exits when its exact primary pane is gone
ok - status bar: Cursor payload normalization is guarded and keeps unknown metrics unknown
ok - status bar: account role is compact, verified, and never an identifier
ok - status bar: herdr companion is session-scoped and exits when its pane is gone
ok - status bar: herdr companion retires once its live pane stops running Codex, keeps a suspended Codex's row, and arms only after seeing Codex
ok - status bar: tmux companion retires once its live pane stops running Codex
ok - status bar: a retiring companion releases the zoom it owns and never one it does not
ok - status bar: the companion clears its pane once and keeps the per-refresh erase
ok - status bar: the companion never blanks its row while collecting a frame
ok - status bar: the companion publishes a row on every refresh
ok - status bar: companion refuses an unverified session provider
ok - status bar: tracked adapters preserve guarded installation across native and companion surfaces
ok - status bar: the rollout's rate-limit block is never reported as this primary's quota
ok - status bar: Codex context tracks the current session across compaction
ok - status bar: Codex context escalates a bounded tail and selects real token events only
ok - status bar: an unrefreshable Codex context keeps its last reading only while it is young enough
ok - status bar: missing, malformed, and stale Codex readings stay unavailable rather than zero
ok - status bar: a Codex quota figure is reported only with its actual windows named
ok - status bar: Codex context binds to one open session and never borrows a sibling
ok - status bar: a tmux pane resolves its Codex primary through a launcher shim and nothing else
ok - status bar: a Codex provider cache miss renders a complete row instead of waiting on the read
ok - status bar: the quota window is rendered only by adapters that actually know one
ok - status bar: chrome mode publishes the border row and keeps the in-pane fallback
ok - status bar: chrome mode only ever releases the zoom, once, and never re-applies it
ok - status bar: the renderer only releases a zoom the launcher reported applying
ok - status bar: an over-long border row loses whole fields visibly and keeps its role marker
ok - status bar: the border row is measured in codepoints regardless of the ambient locale
Evidence: tmux: companion before Codex starts (not armed, stays)

Source: tmux: companion before Codex starts (not armed, stays)

== t+6s: companion started while the pane still runs only a shell (never armed)
%0 cmd=bash h=28
%1 cmd=bash h=1
Evidence: tmux: real Codex running with Astra bar

Source: tmux: real Codex running with Astra bar

== real Codex running in %0; companion bar below it
%0 cmd=node h=28
%1 cmd=bash h=1
-- process tree under pane shell:
28174 28173 bash
-- full window capture:








-- companion row:
⚓ gpt-6-astra·high │ 🧠-- ⚡45%wk │ 🚢0 🧪0 ⏸0 ⚠0 🪦0 📋0 │ 👁 NO-WATCH -- │ $-- │ 💤--
Evidence: tmux: Codex steady, bar kept

Source: tmux: Codex steady, bar kept

== codex alive, 8s later (armed, must not retire)
%0 cmd=node h=28
%1 cmd=bash h=1
Evidence: tmux: Codex suspended (Ctrl-Z), bar kept

Source: tmux: Codex suspended (Ctrl-Z), bar kept

== Codex suspended with Ctrl-Z for 10s (>3 refreshes): shell is foreground, companion must stay
%0 cmd=bash h=28
%1 cmd=bash h=1
28949 T    node
28950 T    ~/.npm-global/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/bin/codex
-- primary pane tail:
bash-3.2$ codex
[1]+  Stopped                 codex
bash-3.2$
-- companion row:
⚓ gpt-6-astra·high │ 🧠-- ⚡45%wk │ 🚢0 🧪0 ⏸0 ⚠0 🪦0 📋0 │ 👁 NO-WATCH -- │ $-- │ 💤--
Evidence: tmux: Claude replaces Codex, companion retires at t+3s

Source: tmux: Claude replaces Codex, companion retires at t+3s

t+1s: %0:claude.exe %1:bash t+2s: %0:claude.exe %1:bash t+3s: %0:claude.exe ... %0 cmd=claude.exe h=30 lines containing gpt-6-astra: 0

== Codex killed, Claude started in the same pane %0 at 00:14:11
t+1s: %0:claude.exe %1:bash 
t+2s: %0:claude.exe %1:bash 
t+3s: %0:claude.exe 
t+4s: %0:claude.exe 
t+5s: %0:claude.exe 
t+6s: %0:claude.exe 
t+7s: %0:claude.exe 
t+8s: %0:claude.exe 
t+9s: %0:claude.exe 
t+10s: %0:claude.exe 
t+11s: %0:claude.exe 
t+12s: %0:claude.exe 
-- processes under pane shell now:
45623 claude
-- primary pane (Claude) capture, head:
 ▐▛███▛█   Claude Code v2.1.292
▝▜██████▀  Opus 5.5 (1M context) · Claude Team
 ▝▝   ▝▝   ~/.no-mistakes/worktrees/9957e108f4d7/01M49M8WW5S5MP0W1BRG5D9PQ0
                                                   tmux detected · scroll with PgUp/PgDn · or add 'set -g mouse on' to ~/.tmux.conf for wheel scroll
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
❯ Try "fix typecheck errors"
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  ⚓ Opus 5.5 (1M context)·medium │ 🧠-- ⚡-- │ 🚢0 🧪0 ⏸0 ⚠0 🪦0 📋0 │ 👁 NO-WATCH -- │ $0.00 │ 💤--
  ⏵⏵ auto mode on (shift+tab to cycle) · ← for agents
== final layout: one pane, full height, no stale Astra row
%0 cmd=claude.exe h=30
lines containing gpt-6-astra: 0
Evidence: herdr: companion before Codex, zoomed by launcher

Source: herdr: companion before Codex, zoomed by launcher

== herdr: companion running, primary w1:p1 still only a shell (never armed), zoomed by launcher
[{"pane_id":"w1:p1"},{"pane_id":"w1:p2"}]
{"zoomed":true}
Evidence: herdr: real Codex running with Astra bar

Source: herdr: real Codex running with Astra bar

== herdr: real Codex started in w1:p1
[{"pane_id":"w1:p1"},{"pane_id":"w1:p2"}]
{"zoomed":true}
-- process-info w1:p1:
{"shell_pid":57992,"fg":[{"pid":68093,"name":"codex"},{"pid":68092,"name":"node"}]}
-- companion pane w1:p2 visible text:
-- companion pane w1:p2 visible text:
⚓ gpt-6-astra·high │ 🧠-- ⚡45%wk │ 🚢0 🧪0 ⏸0 ⚠0 🪦-
-- primary w1:p1 (Codex) visible text:
  Cannot use the background server
  Experimental feature request failed
  1. Run without daemon this time
     Restart with these settings (disabled)
› 2. Cancel
-- w1:p1 border title (chrome row) via pane get:
{"pane_id":"w1:p1","label":null,"title":null,"agent_status":"idle"}
Evidence: herdr: Codex suspended (foreground = shell), bar kept

Source: herdr: Codex suspended (foreground = shell), bar kept

== herdr: Codex suspended with Ctrl-Z for ~11s (>3 refreshes); foreground group is the shell only
{"shell_pid":57992,"fg":[{"pid":57992,"name":"zsh"}]}
68092 T    node
68093 T    ~/.npm-global/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/bin/codex
-- panes:
[{"pane_id":"w1:p1"},{"pane_id":"w1:p2"}]
{"zoomed":true}
-- companion row:
⚓ gpt-6-astra·high │ 🧠-- ⚡45%wk │ 🚢0 🧪0 ⏸0 ⚠0 🪦-
-- primary tail:
zsh: suspended  codex
larstolhurst@Larss-MacBook-Air 01M49M8WW5S5MP0W1BRG5D
9PQ0 %
Evidence: herdr: Claude replaces Codex, companion retires at t+4s and zoom cleared

Source: herdr: Claude replaces Codex, companion retires at t+4s and zoom cleared

t+3s: panes=["w1:p1","w1:p2"] zoomed=true t+4s: panes=["w1:p1"] zoomed=false -- process-info w1:p1: fg=[node, claude.exe] w1:p1: 0 occurrences of gpt-6-astra

== herdr: Codex killed and Claude started in the same pane w1:p1 at 00:15:53
t+1s: panes=["w1:p1","w1:p2"] zoomed=true
t+2s: panes=["w1:p1","w1:p2"] zoomed=true
t+3s: panes=["w1:p1","w1:p2"] zoomed=true
t+4s: panes=["w1:p1"] zoomed=false
t+5s: panes=["w1:p1"] zoomed=false
t+6s: panes=["w1:p1"] zoomed=false
t+7s: panes=["w1:p1"] zoomed=false
t+8s: panes=["w1:p1"] zoomed=false
t+9s: panes=["w1:p1"] zoomed=false
t+10s: panes=["w1:p1"] zoomed=false
-- process-info w1:p1:
{"shell_pid":57992,"fg":[{"pid":92933,"name":"node"},{"pid":82342,"name":"claude.exe"}]}
-- primary w1:p1 (Claude) visible text, head:
▗ ▗   ▖ ▖  Claude Code v2.1.292
           Opus 5.5 (1M context) · Claude Team
 ▘▘   ▝▝   ~/…/01M49M8WW5S5MP0W1BRG5D9PQ0
                                 ◐ medium · /effort
─────────────────────────────────────────────────────
❯ Try "refactor fm-test-run.sh"
─────────────────────────────────────────────────────
  ⚠ Transcript saving is off — inherited CLAUDE_CO…
-- occurrences of gpt-6-astra anywhere in session panes:
w1:p1: 0

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 6 of 7 scenarios driven live against the product
Scenario Result Live Evidence
herdr: the captain swaps the Astra/Codex primary for Claude in the same pane, and the stale gpt-6-astra bar disappears without coming back ✅ pass live herdr-04-retire-after-claude.txt: companion pane w1:p2 gone at t+4s; w1:p1 runs claude.exe; no pane shows gpt-6-astra
tmux: the captain swaps Codex for Claude in the same pane, the companion retires, and Claude gets the full pane height ✅ pass live tmux-05-retire-after-claude.txt: %1 gone at t+3s; %0 claude.exe h=30; 0 gpt-6-astra lines
A Codex primary that is running keeps its bar (no false retirement) ✅ pass live tmux-02/03 (bar kept for over 20s with Codex running); herdr-02 (bar rendered with Codex in the foreground)
Adversarial: Codex suspended with Ctrl-Z for more than 3 refreshes keeps its bar (herdr foreground group is only the shell) ✅ pass live herdr-03-codex-suspended.txt (fg=[zsh], Codex state T, w1:p2 still rendering after 11s); tmux-04-codex-suspended.txt (bar kept after 10s)
Adversarial: a companion started before Codex has exec'd (launch window, retirement not armed) is not retired ✅ pass live tmux-01-before-codex.txt and herdr-01-before-codex.txt: companion pane still present 6s after start with only a shell in the primary pane
herdr: the launcher's zoom on the primary is off after the companion retires, so later splits are not hidden ✅ pass live herdr-04-retire-after-claude.txt: zoomed=true until t+3s, zoomed=false from t+4s once w1:p2 retired; the herdr call log in tests/fm-status-bar.test.sh shows the renderer itself sends pane zoom --off
Adversarial: a retiring companion that does not own the zoom (no --chrome-zoomed) never sends a zoom release ⏸️ untested no Can't be observed live: herdr 0.7.4 clears a tab's zoom itself whenever a pane in that tab closes, so the renderer's own call can't be told apart from the resulting zoom state. Proving it live would n…
  • bash tests/fm-status-bar.test.sh (targeted status-bar suite including retirement, suspended-Codex and zoom-ownership cases) — exit 0
  • tmux lab: bin/fm-lab-home.sh create $LAB, tmux -L fm-lab new-session -s primary with FM_HOME=$LAB, companion split with the launcher's exact tmux command (exec env FM_HOME=… FM_PRIMARY_HARNESS=codex bin/fm-status-bar.sh --adapter codex --model gpt-6-astra --effort high --follow-pane %0 --follow-backend tmux) before Codex started
  • tmux lab: real codex started in %0, waited 20s for steady state, send-keys C-z and waited 10s, then kill -9 %1; wait; claude in the same pane, polling list-panes every second; tmux kill-server + rm -rf $LAB
  • herdr lab: bin/fm-herdr-lab.sh name/provision, workspace create, pane split w1:p1 --direction down --ratio 0.93, pane zoom w1:p1 --on, pane run w1:p2 with the launcher's herdr command plus --chrome-pane w1:p1 --chrome-zoomed and FM_STATUS_HERDR_SESSION
  • herdr lab: real codex in w1:p1, pane send-keys w1:p1 ctrl+z with an 11s hold (checked process-info foreground group = shell only, Codex state T), then kill -9 %1; wait; claude, polling pane list and pane layout .zoomed every second; bin/fm-herdr-lab.sh teardown + rm -rf $LAB
✅ **Document** - passed

✅ No issues found.

⚠️ **Lint** - 1 warning
  • ⚠️ linter found issues (exit code 1)
✅ **Push** - passed

✅ No issues found.

@Amplify-Logic
Amplify-Logic merged commit 8d87184 into main Oct 6, 2026
44 of 46 checks passed
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