acpmux: Send now stops Claude's step, not the turn, so a steer is read at once
Problem
Leo wants a Send now on a message steered into a running turn, as Claude Code desktop has (2026-10-20). A steer waits until Claude Code's current step ends, which can be minutes into a long tool call. Send now should stop that step so Claude reads the message right away. Only the user does this; the composer's keys never interrupt (#22741).
acpmux had no way to do it:
- Stop (
hub/cancel.rs) does make Claude Code read an unread steer. But it marks the turn cancelled, so the chain ends "stopped", and its 10 s forced end would cut off the steer while Claude runs it. queue_edit send_nowon a queued prompt only steers it in for a steering agent, so it waits for the step too.
Change
- Claude adapter (
claude_stdio/outbound.rs):- A steer with
_meta.interruptwrites the line and Claude Code's interrupt together. - A
session/cancelwith_meta.steerNowwrites the interrupt only while a steer is unread, and does not set the Stop flag. - Claude Code then answers the stopped step and reads the steer as one more turn of the prompt. The existing unread-steer path keeps the turn open, and the turn ends the way the agent ends it (
end_turn).
- A steer with
- Hub (
queue_edit.rs,steer_end.rs):send_nowof a prompt that is no longer queued (already steered in) sends the steerNow cancel. A queued prompt is steered in withinterrupt, so it no longer waits either.- Only Claude's adapter gets the steerNow cancel, since another agent would take a cancel as Stop. Other agents answer
ok: false. - There is no forced end; Stop keeps its own.
- Only Claude's adapter gets the steerNow cancel, since another agent would take a cancel as Stop. Other agents answer
- The pane's button comes in a follow-up, next to message-actions' waiting ✓ (#22773). It calls the same
queue_edit send_nowwith the steer'spromptId.
Claude Code's order of lines, observed live with claude -p --input-format stream-json --replay-user-messages (2.1, haiku): a steer written during sleep 25, then an interrupt. The stopped step's error_during_execution result came first. Then the steer's echo, its reply, and a success result. fake_claude.py's new "long-step" replays that order.
Verification
cargo test -p acpmux --lib claude_stdio::steer_now_tests: adapter tests for steer-with-interrupt, steerNow with and without an unread steer, and Stop still ending cancelled.cargo test -p acpmux --test hub_integration steer_now: end to end through the hub with fake_claude.py. Send now on a waiting steer and on a queued prompt; the agent reads it and the turn endsend_turn. Stop still endscancelled.- Test commit ad34433a first, then the fix 715aca9f. Fleet results go below.
Observed / Not run
- Observed: Claude Code's line order (above), live.
- Not run yet: the cargo tests. They run on the fleet, never on Leo's Mac; results will be added here.
- Known edge: Send now while a permission prompt is open stops the step, but this PR does not withdraw the prompt's card. Stop does withdraw it.
Changelog
Changed: Send now on a waiting message stops the agent's current step so it reads the message at once; the turn keeps going.
Agent session: cosmic-gray
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Send now on a message steered into a running turn now stops Claude Code's current step so the message is read immediately, instead of waiting until the step ends (which could be minutes into a long tool call). The turn keeps running and ends as the agent ends it, not as stopped.
- A steer with
_meta.interruptis written together with Claude Code's interrupt; the turn continues and ends withend_turn. send_nowon an already-steered prompt sends asteerNowcancel, and a queued prompt is now steered in withinterrupt, so neither waits.- Only the Claude adapter gets the
steerNowcancel; other agents answerok: falsesince their cancel would mean Stop. Stop keeps its forced end. - The pane's Send now button is a follow-up; it calls the same
queue_edit send_nowwith the steer'spromptId.