fix(acpmux, agent pane): decide Ctrl-B at spawn, and gate subagent Stop on backgroundsWork
Follow-up to #23211, which added backgroundsWork to acpmux's session summary.
The bugs
1. A config write hid Ctrl-B mid-turn. The summary builder (views.rs) read the hub config with try_read() and fell back to false when the lock was busy. A mode or model pick saves the config under its write lock (note_mode_pick, note_pick), and tokio's try_read also fails while a writer waits behind a reader. Each _acpmux/session_changed resends the summary and the webview replaces its copy, so one false hid Ctrl-B until the next notice.
2. A reload could flip a running chat. The answer came from the current config, not from the kind the chat was spawned with. A reload that changed a profile's kind changed a running chat's answer, and the _acpmux/process/background path read the config separately.
3. Subagent Stop went by harness name. The webview offered Stop on running subagents only when harness === "claude". That left the model router's Claude Code chat (harness cmux) with no Stop, and showed Stop on a peer's or Cloud chat named claude, where this hub can't serve it.
The fix
- acpmux:
Session::backgrounds_work(anAtomicBool) is set at each start inensure_child, from the kind of the profile the chat really spawned with (ClaudeStdiomeans true). A host adopted after a daemon restart (adopt_one, both at daemon start and inreadopt), or reached again throughensure_child'sreadoptearly return, sets it from what the host really runs: only Claude Code's translator answers the host's state query with modes. The query runs after the session's inbound loop drains the replay. A host that doesn't answer falls back to the profile's kind (Hub::note_host_kind). A chat with no agent yet starts from its savedagentInfo. The summary and the Ctrl-B accept path both read this one value without a lock, so what acpmux accepts and what it advertises always match. Thetry_readfallback is gone.session_enforcementis untouched. - webviews: subagent Stop uses
backgroundsWork(summary), the same check as Ctrl-B. That check also excludes peer and Cloud chats. BothsessionSummaryandnewSessionSummaryin the mock fixture now includebackgroundsWork.
Tests (written first, red before the fix)
processes::a_chats_ctrl_b_goes_by_the_kind_it_started_as: acmux(claude-stdio) chat runs a foreground shell. With the config write lock held, its summary still saysbackgroundsWork: true. After the profile's kind is switched toacpin the live config, the summary still says true and Ctrl-B still moves the work.durable_agents::an_adopted_claude_chat_still_moves_its_work_after_an_update_restart: acmuxchat runs a foreground shell. The daemon is SIGTERMed, the chat's savedagentInfois removed, and a new daemon adopts the live host. The summary saysbackgroundsWork: trueand Ctrl-B moves the work. Red at 66686cf5cbd (backgroundsWork: false), green at the head.durable_agents::a_claude_host_with_a_long_backlog_is_adopted_at_once_and_still_moves_its_work: the host streams 8,000 deltas while the daemon is down. Adoption takes under 4 s, the summary saysbackgroundsWork: true, and Ctrl-B moves the work. Red at 4a09b0c ("adoption took 5.47s"), green at the head.durable_agents::a_reattached_claude_host_still_moves_its_work: goes throughensure_child'sreadoptearly return after a lost link. Red with the tests on f1f1374 (backgroundsWorkfalse), green at the head.agent-pane-subagents-tab.test.ts: the model router's chat (harnesscmux,backgroundsWork: true) shows Stop on a running subagent. An ACP agent namedclaude-acp(backgroundsWork: false) shows none.
Observed / Not run
- Observed on cmux16:
cargo test -p acpmuxat the head:--test durable_agents31/31 (the three restart tests also passed 3 more runs),--test hub_integration processes::39/39 andbackground23/23, libbackground11/11.- Each new test was red at its test-only commit (output above).
cargo fmt --checkis clean.clippy --no-deps -D warningserrors only on base code:steer_now_tests.rs,self_command.rs,tests/prompt_images.rs.- Web, with
CMUX_BROWSER_TESTS=1:agent-pane-subagents-tab,agent-pane-ctrl-b-backgroundandrun-in-background, 30/30.
- Not run locally: the webviews typecheck (cmux16 has no node). CI runs it.
Changelog
- Internal: acpmux decides Ctrl-B once per agent start, and the agent pane offers subagent Stop wherever acpmux serves it.
🤖 Generated with Claude Code
Agent session: cosmic-gray