fix(sdk): don't end the run while background subagents are still running (#1499)#1500
Open
conancain wants to merge 1 commit into
Open
fix(sdk): don't end the run while background subagents are still running (#1499)#1500conancain wants to merge 1 commit into
conancain wants to merge 1 commit into
Conversation
runClaudeWithSdk breaks the query() loop on the first result message (added in anthropics#1339 to avoid a pull_request hang). Subagents launched via the Agent tool run as background tasks by default (Claude Code >= 2.1.198), so the parent agent's dispatch turn emits a result while the subagents are still running. Breaking on it ends the run and orphans them, so the parent never gets the follow-up turn(s) to consume their output. Track the live background-task set via background_tasks_changed and only break once no background tasks remain live. The SDK's async-agent stall watchdog bounds the wait if a subagent never completes, preserving the anthropics#1339 anti-hang guard. Addresses anthropics#1499 Co-authored-by: Cursor <cursoragent@cursor.com>
JamesScrase-PortSwigger
added a commit
to JamesScrase-PortSwigger/claude-code-action
that referenced
this pull request
Jul 16, 2026
Builds on anthropics#1500. anthropics#1500 keeps the query() loop open while background tasks are live, but a string/one-shot prompt closes the CLI's stdin immediately, and in print mode a background Bash shell is reaped ~5s after the final result once stdin has closed. Subagents are exempt from that reap; bash is not — so anthropics#1500 alone does not save a long background bash (e.g. a test suite), which is always killed before it finishes. Feed the prompt as a stream and hold the generator open until the live background set (reported by `background_tasks_changed`) drains to zero, keeping the process off its exit path. Background bash then runs to completion and the model receives its follow-up turn(s) — matching interactive behavior. The no-background path is unchanged (break on the first result with an empty live set), preserving the anthropics#1339 anti-hang guard. Validated locally against runClaudeWithSdk: a run_in_background sleep-12 Bash task is reaped (never completes) on anthropics#1500-only, and runs to completion on this change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 20, 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.
Summary
runClaudeWithSdk(base-action/src/run-claude-sdk.ts) breaks thequery()loop on the firstresultmessage — added in #1339 to avoid apull_requesthang. But subagents launched via theAgenttool run as background tasks by default (Claude Code ≥ 2.1.198), so the parent agent's dispatch turn emits aresultwhile the subagents are still running. Breaking on it ends the run and orphans them, and the parent never gets the follow-up turn(s) to consume their output. Full write-up in #1499.This tracks the live background-task set via the
background_tasks_changedsystem message and only breaks once no background tasks remain live. The SDK's async-agent stall watchdog bounds the wait if a subagent never completes, so the #1339 anti-hang guard is preserved. With no background subagents (e.g. tag mode) the behavior is unchanged — break on the first result.Fixes #1499
Known limitation
This fixes the primary failure (terminating on the first
resultwhile subagents run). A narrower race can remain: when multiple subagents finish near-simultaneously, the live set can drain to empty while a per-task follow-up turn (the one that consolidates) is still queued, so the loop may break on an interim follow-upresultbefore the final turn. Fully closing that likely needs SDK-level knowledge of whether more follow-up turns are pending (discussed in #1499). Happy to iterate if you'd prefer a different approach.