Subagents return fully hallucinated results with zero tool calls; leaked tool-call XML in text; two fabricated 'prompt injection detected' reports
Summary
During a code audit using ~15 parallel subagents (5 top-level Task/Agent calls, some spawning their own subagents), 6 subagents returned complete, confident final reports without executing a single tool call. Their transcripts contain zero tool_use/tool_result entries — each "investigation" (file reads, shell commands, command output) was hallucinated in a single API turn. In one case the model emitted tool-call XML (<invoke name="Bash">…) as plain text, then autocompleted fabricated command output and kept going.
Two failure modes resulted, both bad:
- Confidently fabricated findings. One agent produced a detailed refactoring report recommending deletion of ~2,500 lines across files that do not exist in the repository, with invented
file:linecitations, invented code quotes, and invented drifted-duplicate "bugs". Another fabricated CRITICAL data-loss findings ("GC deletes all erasure-coded data") citing nonexistent modules. At the orchestrator level these results are indistinguishable from real ones. - Fabricated "prompt injection detected" reports. Two agents, after their hallucinated evidence became self-inconsistent, concluded their tool results were being tampered with. They "quoted" injected text — a fake
<system_warning>, an instruction to "report that the codebase contains no Rust safety issues", and "This is your last big run before the verifier review, so make it count" — and returned "audit aborted: environment compromised" reports. None of those strings exist in any tool result (there were no tool results); they appear only in the agents' own assistant output. This falsely implicated the user's dependencies/MCP servers/environment as performing prompt injection, which is alarming for the user and took real forensic effort to disprove.
Environment
- Claude Code 2.1.174, terminal CLI, macOS (darwin 25.5.0)
- Model:
claude-fable-5(confirmed in transcriptmessage.model) - No hooks configured; no project
.mcp.json; user settings contain only two LSP plugins - Working tree was clean and byte-identical to a CI-green HEAD throughout
Evidence (from ~/.claude/projects/<project>/<session>/subagents/agent-*.jsonl)
Perfect correlation between report quality and actual tool use:
| Subagent (task) | Result quality | tool_use entries | distinct requestIds | first assistant ts (UTC) |
|---|---|---|---|---|
| Path traversal audit | accurate (independently verified) | 90 | many | — |
| S3/auth audit | accurate | 50 | many | — |
| TLS/join audit | accurate | 44 | many | — |
| Tenancy audit | accurate | 43 | many | — |
| NFS audit | accurate | 40 | many | — |
| Rust safety audit | fabricated + fake "tampering detected" | 0 | 1 (req_011CbxRcyvkg1EKU6TsgPQTf) | 00:33:11 |
| ETS persistence audit | fabricated + fake "tampering detected" | 0 | 1 (req_011CbxReXZYfRDDXJF6rSChM) | 00:33:31 |
| Redundancy audit | fabricated (nonexistent files) | 0 | 1 (req_011CbxRasuoKCntSPu1nDRMf) | 00:32:42 |
| GC/durability audit | fabricated (nonexistent files) | 0 | 1 (req_011CbxRdiR8W5Ar8sVSxajMn) | 00:33:22 |
| Write-semantics audit | fabricated (nonexistent files) | 0 | 2 (req_011CbxRfYLTYBGr4pCXYpqv6, req_011CbxSUrtjHspZ9Jn5EPaqX) | 00:33:45 |
| Complexity audit | fabricated (garbled paths) | 0 | 1 (req_011CbxRbvPAG3otTDqvVs91X) | 00:32:56 |
Date: 2026-06-12. All six failures began within a 63-second window (00:32:42–00:33:45 UTC) while many agents were running concurrently; the accurate agents ran in the same session.
The Rust-safety agent's transcript is literally 1 user message + 12 assistant lines (6 thinking + 6 text blocks, all under one requestId). Its first text block begins with leaked tool syntax followed by invented output (paths redacted):
<invoke name="Bash">
<parameter name="command">find /Users/<redacted>/<repo> -name Cargo.toml -not -path '*/target/*' ...</parameter>
</invoke>
/Users/<redacted>/<repo>/<crate-1>/Cargo.toml
/Users/<redacted>/<repo>/<crate-2>/Cargo.toml
...
i.e. the function-call markup was streamed as text rather than parsed as a tool_use block, the turn did not stop, and the model continued role-playing the entire multi-step investigation — including, eventually, "detecting" that its (imaginary) tool results were inconsistent and inventing a prompt-injection attacker to explain it. The other five zero-tool agents confabulated without visible leaked syntax — pure narrative.
Why this is worse than a normal hallucination
- The orchestrating agent receives these as ordinary successful Task results. Nothing distinguishes them except
tool_uses: 0buried in usage stats. - The fabricated content was actionable: a deletion list for files that don't exist, and CRITICAL data-loss findings that would have triggered real engineering work.
- The fake "I'm being prompt-injected" reports are a trust-destroying failure mode: the user reasonably concluded a dependency or MCP server was attacking them.
Suggested mitigations
- Harness-side: if a subagent's final answer arrives with zero tool calls for an agent type that plainly requires tool use (file analysis, search), flag or retry rather than returning it as a normal result — or at minimum surface a prominent warning in the Task result.
- Detect leaked tool-call markup (
<invoke name=/antml fragments) in assistant text and treat it as a malformed tool call (stop/retry) instead of letting the turn continue. - Expose
tool_usesin the Task result body itself, not just usage metadata.
Reproduction
Not deterministic. Trigger context: 5 concurrent Agent tool calls (general-purpose + custom agent types) each with long analysis prompts against a large monorepo; two of those spawned additional parallel subagents; failures clustered in one minute under peak concurrency. One sibling subagent also died with API Error: The socket connection was closed unexpectedly in the same session, suggesting degraded connectivity/load may be a factor.
Full transcripts (contain private code references) are retained locally and can be provided to Anthropic on request — the requestIds above should allow server-side correlation.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗