Repository navigation
fix(server): Claude steer interrupts a running command instead of waiting for it - #15868
nekohasekai wants to merge 1 commit into
Conversation
…ting for it A user's steer interrupts the Claude turn without closing the query, then offers the message, as Esc then send does in Claude Code. The existing active-steering handling keeps the turn open across the aborted result. Agent and scheduled-task steers keep the old behavior. The query declares perTaskStopAffordance so the interrupt spares background subagents. Fixes pingdotgg#15720.
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — The adapter changes existing Claude steer behavior and introduces an unconditional default for how interrupts handle background work across Claude queries. Despite focused tests and documentation, this product-default change warrants human review. You can add or adjust custom eligibility rules. Learn more. |
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. 🧰 Additional context used📚 Code guidelines (2)No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (5)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthroughClaudeAdapterV2 now interrupts the active query before offering user-created steers without a scheduled task ID. Agent-created and scheduled-task steers skip the interrupt. If an interrupt fails, the steer is still offered. Tests, a transcript fixture, and provider documentation reflect the behavior. ChangesClaude turn steering
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix · Severity of issue fixed: Medium Sequence Diagram(s)sequenceDiagram
participant ClaudeAdapterV2
participant queryRuntime
participant ClaudeQuery
ClaudeAdapterV2->>queryRuntime: interrupt active query
queryRuntime->>ClaudeQuery: query.interrupt()
ClaudeAdapterV2->>queryRuntime: offer steer with priority now
Suggested reviewers: Merge Risk: ⚪ Minimal · up to The changed steering behavior is ready to merge after normal checks; no actionable issue remains identified. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to Steering now interrupts foreground work while preserving background work. Stop remains the intended way to end all work. No new permission bypass was established, but overlapping requests and failure recovery are not fully verified. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Full details: Description checkExplanation The description clearly covers the problem, implementation, and detailed verification. However, the required scope-and-approval information is incomplete: it links issue Full details: Docstring CoverageExplanation Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 3 files. (2 skipped: 2 unsupported.)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Fixes #15720.
With Follow-up behavior set to Steer, a Claude steer sent while the agent was running a foreground command was read only after the command returned, however long that took. The adapter offered the message with priority
now, and Claude Code does not cut off a running tool for that.Fix
query.interrupt, the query stays open) and then offers the message, as Esc then send does in Claude Code. The adapter already keeps a steered turn open across the aborted result that follows, so the steer runs next in the same turn.perTaskStopAffordance. Without it, Claude Code stops background subagents on every interrupt, so each steer would kill them. Claude Code requires that the user can still stop background work; in T3, Stop does that by closing the Claude process. The declaration also applies to Cancel on an approval card, which interrupts the turn too: Cancel no longer stops background subagents, and Stop still ends everything.message_steeringreplay recorder mirrors the new order, and its Claude fixture is re-recorded against Claude Code 2.1.289.docs/user/providers-claude.mdsays that a steer stops the running command and that Queue lets it finish.Not changed
Verification
claude_result_is_error, which fails locally on main as well.curlwaits on a held endpoint: answered 52.5 s after it reached the CLI before (after the command was released at 45 s), 2.0 s after. Same provider turn, completed.perTaskStopAffordancethe steer's interrupt stopped the background subagent; with it, the subagent kept running. A foreground subagent is cancelled by the steer, as with Esc in Claude Code, and its rows settle.nowmessage with or without the interrupt, so this case is unchanged.Before / after (iOS Simulator, isolated servers, Claude Code 2.1.289, Sonnet 5.5). Claude runs a slow audit script; three seconds in, a steer asks to skip it. Left, main: answered 30.3 s after the steer, once the script had finished. Right, this fix: the script is interrupted and the steer is answered 4.0 s after it was sent.
Video: https://github.com/nekohasekai/t3code/releases/download/pr-evidence-claude-steer-interrupt-20261005/steer-before-after.mp4
Relation to #15799
#15799 takes the same approach (it ports #12541) and was opened first. This PR differs in two behaviors, both checked against the real CLI:
perTaskStopAffordance. fix(claude): Steer reaches the agent while a command is running #15799 does not, so its interrupt also stops a background subagent that runs while the root thread waits on a foreground command (the Agent SDK run above).Prepared with Claude Opus 5.5 in T3 Code (Claude Code harness).