Complete a background subagent that settles after its turn - #331872
Open
Ryan Ewen (RyanEwen) wants to merge 2 commits into
Open
Complete a background subagent that settles after its turn#331872Ryan Ewen (RyanEwen) wants to merge 2 commits into
Ryan Ewen (RyanEwen) wants to merge 2 commits into
Conversation
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.
Contributor
📬 CODENOTIFYThe following users are being notified based on files changed in this PR: TylerLeonhardtMatched files:
|
Contributor
There was a problem hiding this comment.
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.
Mohammad javad Dianat (dianatofficial)
left a comment
There was a problem hiding this comment.
Well done. The optimization reduces unnecessary allocations.
This was referenced Aug 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
ClaudeSdkPipelinederives the turn id for every SDK message from the prompt queue head:and
ClaudeSdkMessageRouter.handlereturns 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 itssystem.task_notificationnever becomes asubagent_completedsignal.This contradicts the contract
SubagentRegistrydocuments for itself, that background spawns survive across turns by design because their completion arrives later viasystem.task_notification. The foreground path is unaffected, since itstool_resultarrives 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
completeSubagentSessionresolves 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_notificationwith no turn id, and asserts asubagent_completedsignal 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.