fix(server): a steered send no longer leaves a subagent task running#19019

merged2
main
GitHub fork: fix/19002-steered-send-node
opened18d agobySScottN-PV
View on GitHubTwo-way sync enabledPR last imported 10d ago

Problem

t3_thread_send into a delegated child records a follow-up task on the parent (recordSubagentFollowup in Orchestrator.ts) and binds it to the child run that received the message. When the send steers into a running child run that has no task of its own, the binding is written mid-run. The run's terminal write in RunExecutionService.ts starts from the run snapshot taken when its attempt started, so the binding is gone once the run ends. No terminal run matches the task, and the parent row stays running: the composer keeps "Waiting on subagent", and Stop and task_cancel cannot clear it.

Runs without a task of their own include those started by a notification wake, a scheduled task, a restart continuation, or a follow-up sent before the original result was published. A steer into a run that already has a task returns that task and creates nothing, so the triage's "absorbing run keeps its earlier delegatedTaskId" case is not where the stuck row comes from.

Change

recordSubagentFollowup gives a send its own task only when the same command created a run or a new attempt. A steer that joins the running attempt gets no task.

Contract: a steer into a running turn that already has a task still returns that task's id. A steer into a running turn with no task now returns delivery: "steered" without a taskId, instead of a task that never settles. A send that restarts a turn with no task, including the automatic restart fallback that also reports delivery: "steered", still gets its own task, and the restarted run keeps it to the end. The t3_thread_send tool description now says which sends return a taskId.

Carrying a mid-run binding through the terminal write instead would change which fields that write takes from the live run. Its comment covers only delegatedCompletion, and widening it is more change than this bug needs.

Surface: the parent composer's "Waiting on subagent" chip, which renders the server's subagent rows. No UI code changes.

Scope and approval

Refs #19002. Maintainer triage confirmed the bug in this comment. This stops new steered sends from leaving a row behind. It does not make Stop clear rows that are already stuck, because Stop has no settlement write today.

Verification

Box check on Ubuntu 24.04, head 845975728a, base 6524a8d437:

  • 69 tests pass in apps/server/src/mcp/OrchestratorMcpService.test.ts, apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts, apps/server/src/mcp/toolkits/orchestrator/tools.test.ts, apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts, apps/server/src/orchestration-v2/ProjectionRecovery.test.ts, apps/server/src/orchestration-v2/SteeringCompletion.integration.test.ts.
  • With apps/server/src/orchestration-v2/Orchestrator.ts, apps/server/src/mcp/toolkits/orchestrator/tools.ts reverted to 6524a8d437, 1 test fails with assertion errors: "orchestrator MCP toolkit > delegates cross-provider tasks, polls and cancels children, and creates ordinary threads".
  • vp fmt --check and vp lint report no errors and no new warnings on the 3 changed TS files.
  • Typecheck passes for t3.

The toolkit test delegates a Claude child, starts a server-sent run on it, steers into that run with t3_thread_send, waits until the fake provider receives the steer, then ends the run. On main the send returned a task id, the run carried it until it ended and lost it after, and the parent row stayed running (3 of 3 runs). The control is a Codex session with active steering turned off: the send restarts the turn, gets a task, the run keeps it, and the task settles as completed. The test ends the steered run with an interrupt because the fake provider holds that turn open.

Model: Claude Opus 5.5, Claude Fable 5.1 (review), GPT-6-Astra (review). Harness: Claude Code, Codex.

Checks

24 successful · 7 skipped
All checks passed
8459757

Added 1 commit

  • fix(server): a steered send no longer leaves a subagent task runningd282e02
Synced from GitHub

Approvability

Verdict: Approved at d282e02

Macroscope's review found this PR approvable — This is a narrowly scoped server bug fix that prevents steered sends from creating orphaned subagent tasks while preserving task tracking for new or restarted runs. Targeted integration tests cover both paths, and the other changes are documentation or test harness updates.

You can add or adjust custom eligibility rules. Learn more.

Added 1 commit

  • fix(server): keep the tested follow-up wording in the t3_thread_send description8459757
Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 35f582d2-bb7e-4bd5-a5ad-10c47ad8deda
📥 Commits

Reviewing files that changed from the base of the PR and between 6524a8d43795dc543f7cfb572388cdea3662ad48 and 845975728aaa0e248b166970e862d3d01dab281e.

📒 Files selected for processing (3)
  • apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts
  • apps/server/src/mcp/toolkits/orchestrator/tools.ts
  • apps/server/src/orchestration-v2/Orchestrator.ts

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

The orchestrator now creates subagent follow-up tasks only when a message creates a run or a new attempt. The MCP tool description and integration tests cover task behavior when steering an active child run.

Changes: Child-thread follow-up recording

Layer / File(s)Summary
Gate follow-up recording
apps/server/src/orchestration-v2/Orchestrator.ts, apps/server/src/mcp/toolkits/orchestrator/tools.ts
recordSubagentFollowup returns unless pending events indicate a new run or attempt. The t3_thread_send description clarifies when follow-ups return a taskId.
Test child-run steering
apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts
The deterministic adapter signals when it receives a steer. Integration tests verify that Claude active steering does not create a follow-up task, while Codex steering with active steering disabled restarts the turn and creates a task.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: juliusmarminge

Merge Risk: ⚪ Minimal · up to 84597

The change appears ready to merge after normal checks. No actionable risk remains from the supplied evidence.

Security architecture risk: 🔵 Low · up to 84597

The change prevents a running turn from acquiring a follow-up task that cannot finish. Existing sending permissions remain unchanged. Risk is low, with incomplete verification of persistent-write failure behavior.

Retained concerns No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The existing tool accepts a caller-selected thread and message within the environment. The PR changes parent-owned follow-up metadata creation, not target selection or provider authority. The inspected comparison establishes no expansion of message-sending reachability.

Trust Boundaries and Controls

  • observed — The send path retains the orchestration-capability requirement, live provider ownership check for a thread-scoped caller sending elsewhere, and runtime and interaction permission ceilings. The orchestrator also rechecks dispatch mode limits under its lock before planning a new command.

Resilience and Maintainability Implications

  • observed — Existing synchronization serializes follow-up intake and Stop on the parent before taking the child lock. Accepted command retries replay stored events, and replay against a different thread is rejected. These controls support task ownership and repeatability without changing authority.
Pre-merge checks · Passed 4
Passed Passed checks (4 passed)
Check nameStatusExplanation
Title checkPassedThe title clearly identifies the server fix: steered sends no longer leave a subagent task running. It is concise and uses the repository's conventional commit format.
Description checkPassedThe description includes all required sections. It explains the problem, the implementation, scope and approval reference, and focused verification results. It also states the known limitation for alr…
Linked Issues checkPassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes checkPassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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