Skip to content

fix(server): Claude steer interrupts a running command instead of waiting for it - #15868

Open
nekohasekai wants to merge 1 commit into
pingdotgg:mainfrom
nekohasekai:agent/fix-15720-claude-steer-waits-bash
Open

nekohasekai wants to merge 1 commit into
pingdotgg:mainfrom
nekohasekai:agent/fix-15720-claude-steer-waits-bash

Conversation

@nekohasekai

Copy link
Copy Markdown

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

  • A steer the user sends now interrupts the turn first (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.
  • The interrupt goes out before the offer, so a tool returning at that moment cannot fold the steer into the aborted turn. If the interrupt fails, the steer is still offered and behaves as before.
  • Steers created by agents (delegated task completions, agent-to-agent messages) and scheduled-task deliveries keep the old behavior and never cut off a command.
  • The query now declares 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.
  • The message_steering replay recorder mirrors the new order, and its Claude fixture is re-recorded against Claude Code 2.1.289.
  • docs/user/providers-claude.md says that a steer stops the running command and that Queue lets it finish.

Not changed

  • Codex, OpenCode and Pi steer natively; Cursor already uses interrupt-and-restart.
  • Promoting a queued message to a steer follows that message's author.
  • The interrupted command shows as failed: Claude Code reports it like a declined tool use, which T3 already shows as failed.

Verification

  • Unit tests: which senders interrupt, that the interrupt reaches the CLI before the offer, and that a failed interrupt still offers the steer. Replay fixtures pass, except claude_result_is_error, which fails locally on main as well.
  • Real T3 server and Claude Code 2.1.289, Sonnet 5.5, steer sent while curl waits 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.
  • Agent SDK against the real CLI, a background subagent running while the root thread waits on a foreground command: without perTaskStopAffordance the 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.
  • A steer while an approval is pending: Claude Code cancels the pending permission request on the now message with or without the interrupt, so this case is unchanged.
  • iOS simulator against isolated servers (video below): the steer was answered 30.3 s after it was sent before, 4.0 s after.

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.

Before and after: the steer waits for the script on main and interrupts it with this fix

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:

Prepared with Claude Opus 5.5 in T3 Code (Claude Code harness).

…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.
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Oct 5, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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.

@coderabbitai

coderabbitai Bot commented Oct 5, 2026

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (2)
docs/internals/effect-services.md — auto-discovered
AGENTS.md — auto-discovered

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: pingdotgg/t3code/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d294d1c1-f343-4e12-b0e4-47146d4852ea
📥 Commits

Reviewing files that changed from the base of the PR and between 1e2ecbd and aa3d698.

📒 Files selected for processing (5)
  • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
  • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts
  • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
  • apps/server/src/orchestration-v2/testkit/fixtures/message_steering/claude_transcript.ndjson
  • docs/user/providers-claude.md

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

ClaudeAdapterV2 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.

Changes

Claude turn steering

Layer / File(s) Summary
Interrupt active turns for user steers
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Claude query options enable the per-task stop affordance. User-created steers without a scheduled task ID interrupt the live query before the message is offered. Interrupt failures are logged and do not prevent the offer.
Validate and document steering behavior
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts, apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts, apps/server/src/orchestration-v2/testkit/fixtures/message_steering/claude_transcript.ndjson, docs/user/providers-claude.md
The test harness records and awaits interrupts before offering steering prompts. Tests cover user, agent, and scheduled-task steers, plus failed interrupts. The fixture records the interrupt and updated transcript. The guide describes interrupting active commands and tools, and the option to queue a message.

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
Loading

Suggested reviewers: juliusmarminge

Merge Risk: ⚪ Minimal · up to aa3d6

The changed steering behavior is ready to merge after normal checks; no actionable issue remains identified.

Security Architecture Review

Security architecture risk: 🔵 Low · up to aa3d6

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
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated control-flow change affects the active query and its existing background work. The inspected change does not establish new tenant, service, credential, or data-store authority. Actual execution exposure remains bounded by the existing query's permissions and environment, which were not comprehensively audited here.

Trust Boundaries and Controls

  • observed — The provider control service forwards creator and scheduled-task metadata from the persisted message. The adapter uses those fields, rather than message text, to select interruption. Its active-turn identity check remains enforced before interruption and delivery.

Resilience and Maintainability Implications

  • inferred — The new asynchronous interruption creates an additional transition before delivery. Adapter-level inspection alone cannot establish ordering across overlapping steers, atomicity with persisted steering effects, or recovery if interruption succeeds but delivery does not. Orchestration records steering as a pending effect; its worker serialization and recovery behavior remain unverified, so no reachable race or orphaned-work finding is asserted.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description clearly covers the problem, implementation, and detailed verification. However, the required scope-and-approval information is incomplete: it links issue #15720 but does not provide ex… Add the maintainer approval comment that confirms the direction and scope, or explain why this focused fix qualifies for the exemption without prior approval.
Docstring Coverage ⚠️ Warning 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… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: Claude Steer interrupts a running command instead of waiting for it.
Linked Issues check ✅ Passed #15720 requires Claude user Steer to interrupt a running command, deliver the steer promptly, and keep the query open. ClaudeAdapterV2.steerTurn interrupts user-authored, non-scheduled steers before…
Out of Scope Changes check ✅ Passed The perTaskStopAffordance option supports the interrupt behavior by preventing interrupts from stopping background subagents. The adapter tests, replay recorder and fixture, and Claude provider docu…
Full details: Description check

Explanation

The description clearly covers the problem, implementation, and detailed verification. However, the required scope-and-approval information is incomplete: it links issue #15720 but does not provide explicit maintainer approval or explain why this change qualifies for the small, obvious fix exemption.

Full details: Docstring Coverage

Explanation

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.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Claude Steer waits for a running Bash command to finish instead of reaching the agent

1 participant