Skip to content

Complete a background subagent that settles after its turn - #331872

Open
Ryan Ewen (RyanEwen) wants to merge 2 commits into
microsoft:mainfrom
RyanEwen:fix/subagent-completion-when-idle
Open

Complete a background subagent that settles after its turn#331872
Ryan Ewen (RyanEwen) wants to merge 2 commits into
microsoft:mainfrom
RyanEwen:fix/subagent-completion-when-idle

Conversation

@RyanEwen

Copy link
Copy Markdown
Contributor

Problem

A subagent spawned in the background keeps its row spinning for the rest of the session even though it finished, and its entry is never evicted from the subagent registry.

Root cause

ClaudeSdkPipeline derives the turn id for every SDK message from the prompt queue head:

const parent = this._queue.peekParent();
const turnId = parent?.turnId;

and ClaudeSdkMessageRouter.handle returns early when there is none, so once the queue drains every message is dropped before reaching the mapper. A background subagent settles after the turn that spawned it has ended, which is precisely when the queue is empty, so its system.task_notification never becomes a subagent_completed signal.

This contradicts the contract SubagentRegistry documents for itself, that background spawns survive across turns by design because their completion arrives later via system.task_notification. The foreground path is unaffected, since its tool_result arrives inside the turn.

Fix

Route the subagent system messages when there is no turn. They carry their own chat and tool call id, so they never needed one, and completeSubagentSession resolves the turn on the subagent's own chat rather than the parent's, so it works while the parent is idle. Turn-scoped messages are still dropped exactly as before. The mapping is pulled into a small helper so both paths share the existing swallow-and-log behaviour.

Testing

Added a router test that records a background spawn, delivers a terminal task_notification with no turn id, and asserts a subagent_completed signal is produced and the spawn is evicted. The existing turn-less assertion is kept, narrowed to the turn-scoped message it actually covers. I could not find existing coverage for a background subagent completing while the parent is idle, and the e2e subagent suite has no background case at all.

AI disclosure: this comment and the related code were written with the assistance of AI.

The router takes its turn id from the prompt queue head and returns
early when there is none, so once the queue drains every SDK message is
dropped before it reaches the mapper. A background subagent settles
after the turn that spawned it has ended, which is exactly when the
queue is empty, so its `system.task_notification` never becomes a
`subagent_completed` signal. Its row spins for the life of the session
and the spawn record is never evicted from the registry.

That contradicts the registry's own contract, which says background
spawns survive across turns by design because their completion arrives
later via `system.task_notification`.

Route the subagent system messages when there is no turn. They carry
their own chat and tool call id, so they do not need one, and
completeSubagentSession resolves the turn on the subagent's chat rather
than the parent's. Turn-scoped messages are still dropped as before.
Copilot AI balanced review requested due to automatic review settings August 20, 2026 21:19
@vs-code-engineering

Copy link
Copy Markdown
Contributor

📬 CODENOTIFY

The following users are being notified based on files changed in this PR:

TylerLeonhardt

Matched files:

  • src/vs/platform/agentHost/node/claude/claudeSdkMessageRouter.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Routes turn-less Claude SDK subagent lifecycle messages so background subagents complete and registry entries are evicted.

Changes:

  • Handles subagent system messages after the parent turn ends.
  • Centralizes signal mapping and error handling.
  • Adds regression coverage for background completion without a turn ID.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
claudeSdkMessageRouter.ts Routes turn-less subagent messages and extracts signal production.
claudeSdkMessageRouter.test.ts Tests deferred background-subagent completion and eviction.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done. The optimization reduces unnecessary allocations.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants