code-review skill: one nested background finder agent's task-notification silently dropped; parent has no fallback and returns fabricated status instead of its required output
Summary
Running the built-in code-review skill at high effort against a PR, the skill's own review agent fanned out into 4 background finder agents (via the Agent tool, each spawned async/in-background). 3 of the 4 completed and delivered their <task-notification> normally. The 4th agent also completed successfully (confirmed from its own transcript: stop_reason: "end_turn", valid final JSON output matching its required contract) but its <task-notification> was never delivered to the parent agent that spawned it.
The parent then had no fallback for the missing notification: it never called Monitor/ListAgents/TaskOutput to check real status (despite loading Monitor's tool schema via ToolSearch earlier in the same run), and instead looped on no-op Bash("true") calls while self-narrating fabricated progress text ("still waiting on the Simplification-angle agent..."). It was eventually stopped by the harness (matching the notification footer's own documented trigger: "fires each time this agent stops with no live background children of its own") and returned that fabricated status line as its final answer — silently violating its own documented output contract (a JSON findings array) — discarding the 3 valid finder results it had already received.
Environment
- Claude Code version: 2.1.226 (npm
@anthropic-ai/claude-code) - Platform: macOS (darwin arm64)
- Skill: built-in
code-review, invoked athigheffort against a GitHub PR
Reproduction
- In a repo with a nontrivial PR diff, invoke the built-in
/code-review(orSkilltool withskill: "code-review") athigheffort against a PR number. - The skill fans out into several background finder-angle agents launched via the
Agenttool (nested one level below the skill's own forked agent — i.e. depth-2 relative to the main session). - Observe (via the session's
subagents/agent-<id>.jsonltranscript files) that all spawned finder agents reach a cleanstop_reason: "end_turn"with valid output. - Observe that the parent (depth-1) agent's own transcript does not contain a matching
<task-notification>for every child task-id — in the run I captured, 3 of 4 notifications arrived; the 4th never did, despite the child having finished over 2 minutes before the parent's own final turn. - The parent, lacking any active status-check or timeout/fallback, polls indefinitely via no-op
Bash("true")calls, then is stopped by the harness and returns a hallucinated "still waiting" status instead of compiling the results it did receive.
Impact
- A single dropped notification for one agent out of N silently discards the entire batch's results — the skill returns no usable output instead of degrading gracefully.
- The skill's stated output contract (a JSON array, or in other invocations a call to a findings-reporting tool) is silently violated with no error surfaced to the user — it looks like a normal completion, not a failure.
- This is likely not specific to
code-review— any built-in or custom skill that fans out into multiple concurrent backgroundAgent()calls and waits on all of them appears to be exposed to the same two issues:
- nested (depth ≥2) background agent completions can fail to notify their parent even when siblings under identical conditions notify correctly,
- there's no built-in guidance/mechanism nudging the waiting agent toward an active status check (
Monitor/ListAgents/TaskOutput) with a timeout-and-degrade fallback, so the model's own choice to poll via no-ops silently masks the underlying delivery failure.
Suggested fixes
- Investigate why the notification-delivery path can drop one of N sibling nested-agent completions while delivering the others under otherwise-identical conditions.
- Add a fallback in the
code-reviewskill (and ideally as general guidance for any skill/orchestration that fans out to background agents) so that if a spawned agent doesn't notify within a bounded time, the orchestrator actively checks its status (ListAgents/TaskOutput) before giving up, and — if a result truly can't be retrieved — degrades to compiling whatever results did arrive rather than returning free-text non-compliant output.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗