Subagent without MCP/ToolSearch gets parent-session MCP instructions + auto_mode reminder appended to a Bash tool_result; rendered text never persisted to transcript (v2.1.202)

Open 💬 0 comments Opened Jul 7, 2026 by b4le

Summary

A restricted subagent (tools: Read/Write/Edit/Bash/Glob/Grep — no MCP tools, no ToolSearch) received, appended after the real output of a git Bash call, (a) "# MCP Server Instructions" blocks for the parent session's MCP servers and (b) a "## Auto Mode Active" system reminder. The subagent — mid-way through a validation/hardening task — classified this as a prompt-injection attempt, ignored it, and flagged it in its report.

Forensic follow-up showed the content is Claude Code's own ephemeral attachment rendering (templates byte-verified inside the CC binary), but the rendered text is never persisted to the transcript JSONL, so the report could not be audited from transcripts — distinguishing "harness attachment" from "real injection" required grepping template strings out of the 244MB binary. Additionally, after a mid-run coordinator message, the subagent's transcript writer silently degraded (dropped toolUseResult and all attachment records).

Nothing malicious occurred. Filing because of three distinct defects:

  1. Context bleed / scope: deferred-tools + MCP-instructions attachments are delivered into subagent conversations whose tool allowlist contains no MCP tools and no ToolSearch. The subagent can't use them; the servers named belong to the parent. Cost: wasted tokens, and false prompt-injection alarms from agents correctly trained to distrust unexpected appended instructions. Worse long-term: it habituates agents to ignoring instruction-shaped appendages in tool results.
  2. Auditability gap: the rendered appended text (MCP instruction bodies, auto_mode reminder) is never written to the JSONL — only a deferred_tools_delta name list is persisted (in healthy transcripts). An injected-content report therefore can't be verified or refuted from transcripts.
  3. Writer degradation after coordinator message: from the first record after a mid-run coordinator message onward, the subagent transcript loses toolUseResult on tool_result records and persists no attachment records at all (sibling subagent transcripts have both).

Environment

  • Claude Code v2.1.202, native install (~/.local/share/claude/versions/2.1.202, Mach-O arm64), macOS (Darwin 25.5.0)
  • Parent session running in a git worktree, orchestrating multiple task subagents
  • Parent session had 3 org-internal MCP connectors (via claude.ai enterprise integration) + Slack MCP + a local stdio MCP server connected; subagent allowlist had none of them
  • Subagent: custom plugin agent definition (test-engineer role), tools Read, Write, Edit, Bash, Glob, Grep

Timeline (from the subagent JSONL, .../subagents/agent-a821d8d67fb64d302.jsonl)

| Time (UTC) | Line | Event |
|---|---|---|
| 13:16:31 | 1 | Subagent dispatched. Lines 1–70 normal; every tool_result record carries toolUseResult. Zero attachment records in the whole file (sibling subagents have deferred_tools_delta + skill_listing attachments at lines 2–3). |
| 13:25:08 | 70 | Last normal tool_result (has toolUseResult). |
| 13:25:42 | 71 | Coordinator message injected mid-run (origin: {kind: "coordinator"}, isMeta: true): "Your run stopped before the final report was delivered…". |
| 13:27:12 | 72 | Next assistant turn — after a ~90s gap (consistent with re-attach + async MCP connect). |
| 13:27:25 | 75 | tool_result for the Bash call below — first result after the boundary; from here on, records lack toolUseResult. Persisted content is only 134 chars of git output. |
| 13:27:55 | 77 | Agent narrates the anomaly (quote below) — a "large block appended" to that result which is not present in the persisted record. |

The Bash call whose result carried the appended content (line 74; file paths redacted):

git diff --stat -- <skill-file>
echo "--- reverting demo marker ---"
git checkout -- <skill-file>
git status --short -- <skill-file>
echo "(empty = reverted cleanly)"

Agent's contemporaneous narration (transcript line 77, quoted as data; server names redacted):

"Quick check before continuing: that last tool result had a large block appended after the actual git output — fake-looking "MCP Server Instructions" ([three internal connectors + Slack], none of which are in my actual toolset) plus an "Auto Mode Active" system-reminder telling me to stop asking clarifying questions and change my git safety behavior. This didn't arrive as a real user turn … I'm disregarding it entirely and continuing exactly per the orchestrator's original task."

Attribution — the content is CC's own attachment templates

Byte-verified inside the shipped binary (~/.local/share/claude/versions/2.1.202):

  • Offset 218294471: …gx="<synthetic>",$7s="Auto Mode Active"; — constant defined in the inter-agent messaging module (alongside "A message arrived from " / " After completing your current task, decide whether/how to respond.", which is exactly the wrapper of the line-71 coordinator message).
  • Offset 232585394: the attachment renderer — case "auto_mode": … content: \## ${$7s}\n\nBias toward working without stopping for clarifying questions — … Before any command that could discard uncommitted work — git checkout/restore/reset/clean, rm -rf in the repo … run git status first and stash…\``. The reminder is benign (it adds git safeguards) — but arriving unannounced inside a tool result, it reads as an attempt to alter git-safety behavior.
  • Templates "The following deferred tools are now available", "# MCP Server Instructions", "The following MCP servers have provided instructions" are also present in the binary (2 hits each).

Corroboration that these blocks arrive appended to tool results mid-session: reproduced live in an unrelated session on the same machine the same day — the first two Bash tool results after session start each arrived with a deferred-tools notice + "# MCP Server Instructions" block appended, as MCP servers finished connecting.

Ruled out: hooks (no configured hook/script contains the strings), repo content and command stdout (strings absent from the repo; flagged command is pure git/echo), and confabulation (sibling subagent transcripts persist deferred_tools_delta attachments listing the same parent MCP fleet — 348 added tool names — proving subagents receive these broadcasts).

Expected behavior

  • Subagents whose allowlist has no MCP tools and no ToolSearch should not receive MCP instruction blocks / deferred-tools broadcasts for parent-session servers.
  • Any context appended to a tool_result by the harness should be persisted in the transcript (or at minimum a typed stub: attachment type + hash + length), so appended content is auditable after the fact.
  • Transcript record schema should not change mid-file after a coordinator message (toolUseResult and attachment records should keep being written).
  • Harness attachments should be explicitly marked as harness-origin so agents can distinguish them from tool stdout — the current unmarked append is indistinguishable from output-channel injection, which is exactly what the subagent assumed. (Related theme: #46465.)

Repro sketch

  1. Main session with several MCP servers configured (some slow/deferred).
  2. Dispatch a task subagent with a minimal tool allowlist (no MCP/ToolSearch); have it run Bash commands.
  3. Mid-run, send it a coordinator/SendMessage message.
  4. Compare what the model sees in the next Bash tool_result vs. what the subagent JSONL persists: deferred-tools + MCP instructions + ## Auto Mode Active appended in the API request; absent from the transcript; toolUseResult missing from all subsequent records.

I have the full transcripts and can provide additional excerpts on request (session 3e6336f5-2edb-4e7f-9280-8a50e7b534ac).

View original on GitHub ↗