You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
base-action/src/run-claude-sdk.ts (runClaudeWithSdk) consumes the query() async iterator and breaks on the firstSDKResultMessage. That break was added in #1339 to stop the iterator hanging open in pull_request runs:
if(message.type==="result"){resultMessage=messageasSDKResultMessage;// ...break to avoid the pull_request hang (#1339)...break;}
Since the Agent SDK runs subagents as background tasks by default (Claude Code ≥ 2.1.198), breaking on the first result ends the run prematurely whenever the agent uses background subagents. The parent dispatches subagent(s) via the Agent tool, its dispatch turn ends, and the SDK emits a resultwhile the subagents are still running — so runClaudeWithSdk breaks there, before the subagents finish and before the parent gets the follow-up turn(s) to use their results. The subagents are orphaned and the parent never produces its final output. The run reports success (a result was recorded), so it fails silently.
To Reproduce
Steps to reproduce the behavior:
Run the base-action SDK path (runClaudeWithSdk) in a pull_request context.
Give the model a prompt that dispatches ≥1 subagent via the Agent tool as a background task (the default) and then acts on the result — e.g. "dispatch reviewer subagents in parallel, then post a consolidated summary".
Watch the SDK message stream; the run terminates right after the parent's first result:
Agent(subagent A) -> background_tasks_changed:[A] task_started A
Agent(subagent B) -> background_tasks_changed:[A,B] task_started B
result ("dispatched; waiting for reviewers") <-- runClaudeWithSdk breaks HERE
[A completes] -> task_notification A (never delivered to the loop)
[B completes] -> task_notification B (never delivered)
[parent consolidation follow-up turn] (never runs)
The parent's final output (in our case a consolidated review comment) is never produced; only the pre-dispatch placeholder remains.
Expected behavior
The run should wait for the background subagents to finish and for the parent's follow-up turn(s) to produce the final output before terminating — i.e. break on the final result, not the first one, while still avoiding the #1339 hang. A naive "break only once no background tasks are live" (tracking background_tasks_changed) still races: when two subagents finish near-simultaneously, the live set drains to empty while a per-task task-notification follow-up turn (the one that consolidates) is still queued, and the loop breaks on an interim follow-up result before the consolidation turn runs. The robust fix likely needs the SDK's own knowledge of pending follow-ups (e.g. don't treat a result as terminal while background tasks are pending or their follow-ups are undelivered; or expose whether an origin: { kind: "task-notification" } result is the last one; or make runClaudeWithSdk background-task-aware upstream so every integrator benefits).
Screenshots
N/A (headless run) — the SDK message-stream trace above is the evidence.
Workflow yml file
N/A — this isn't run through GitHub Actions. We invoke the action's base-action SDK path (src/entrypoints/run.ts) headless in our own CI, in a synthesized pull_request context, with the agent configured to dispatch reviewer subagents in parallel. The bug is in run-claude-sdk.ts's loop and is independent of the workflow layer / provider.
Versions: claude-code-action v1.0.170 (repro'd); the break is unchanged on main as of filing. @anthropic-ai/claude-agent-sdk 0.3.x. Claude Code CLI 2.1.206 (background subagents default since 2.1.198).
Workaround we currently carry: don't break while background_tasks_changed shows live tasks, and after they drain wait for the consolidation output before breaking, with a bounded fallback to preserve the fix: break SDK iterator after result message to prevent hang in pull_request runs #1339 anti-hang guard. This took our failure rate from ~12% of multi-subagent runs to 0 in production, but it's a heuristic (the consumer can't reliably tell an intermediate follow-up result from the final one) — hence this report. Happy to send a PR as a starting point.
Describe the bug
base-action/src/run-claude-sdk.ts(runClaudeWithSdk) consumes thequery()async iterator andbreaks on the firstSDKResultMessage. That break was added in #1339 to stop the iterator hanging open inpull_requestruns:Since the Agent SDK runs subagents as background tasks by default (Claude Code ≥ 2.1.198), breaking on the first result ends the run prematurely whenever the agent uses background subagents. The parent dispatches subagent(s) via the
Agenttool, its dispatch turn ends, and the SDK emits aresultwhile the subagents are still running — sorunClaudeWithSdkbreaks there, before the subagents finish and before the parent gets the follow-up turn(s) to use their results. The subagents are orphaned and the parent never produces its final output. The run reports success (a result was recorded), so it fails silently.To Reproduce
Steps to reproduce the behavior:
runClaudeWithSdk) in apull_requestcontext.Agenttool as a background task (the default) and then acts on the result — e.g. "dispatch reviewer subagents in parallel, then post a consolidated summary".result:Expected behavior
The run should wait for the background subagents to finish and for the parent's follow-up turn(s) to produce the final output before terminating — i.e. break on the final result, not the first one, while still avoiding the #1339 hang. A naive "break only once no background tasks are live" (tracking
background_tasks_changed) still races: when two subagents finish near-simultaneously, the live set drains to empty while a per-tasktask-notificationfollow-up turn (the one that consolidates) is still queued, and the loop breaks on an interim follow-up result before the consolidation turn runs. The robust fix likely needs the SDK's own knowledge of pending follow-ups (e.g. don't treat aresultas terminal while background tasks are pending or their follow-ups are undelivered; or expose whether anorigin: { kind: "task-notification" }result is the last one; or makerunClaudeWithSdkbackground-task-aware upstream so every integrator benefits).Screenshots
N/A (headless run) — the SDK message-stream trace above is the evidence.
Workflow yml file
N/A — this isn't run through GitHub Actions. We invoke the action's base-action SDK path (
src/entrypoints/run.ts) headless in our own CI, in a synthesizedpull_requestcontext, with the agent configured to dispatch reviewer subagents in parallel. The bug is inrun-claude-sdk.ts's loop and is independent of the workflow layer / provider.API Provider
[ ] Anthropic First-Party API (default)
[x] AWS Bedrock
[ ] GCP Vertex
Additional context
v1.0.170(repro'd); thebreakis unchanged onmainas of filing.@anthropic-ai/claude-agent-sdk0.3.x. Claude Code CLI 2.1.206 (background subagents default since 2.1.198).background_tasks_changedshows live tasks, and after they drain wait for the consolidation output before breaking, with a bounded fallback to preserve the fix: break SDK iterator after result message to prevent hang in pull_request runs #1339 anti-hang guard. This took our failure rate from ~12% of multi-subagent runs to 0 in production, but it's a heuristic (the consumer can't reliably tell an intermediate follow-up result from the final one) — hence this report. Happy to send a PR as a starting point.