Messaging a running subagent re-injects its full frontmatter skills: bodies and re-fires SubagentStart hooks — undocumented and token-costly
Summary
Every message sent to an already-spawned subagent (via SendMessage / the teammate mailbox — i.e. a resume or mid-task nudge, not a fresh Agent call) re-fires the entire SubagentStart hook chain and re-renders the subagent's frontmatter skills: bodies into its context, byte-identical to the spawn-time injection. For agents with substantial skill preloads this re-pays tens of KB of input tokens per nudge, and nothing in the docs or changelog describes this cadence. We could guard our own hooks, but the frontmatter-skills rendering is a native channel outside hook control.
Environment
- Claude Code 2.1.215 (macOS, darwin 25.x) at observation time; docs and changelog re-checked through 2.1.217 — no related change noted.
- Custom subagents defined in
.claude/agents/*.mdwith frontmatterskills:(native context injection since v2.1.183).
Reproduction
- Define a subagent in
.claude/agents/foo.mdwith frontmatterskills:listing skills whose bodies total tens of KB. - Optionally register a SubagentStart hook that emits any marker text.
- Spawn the agent with the Agent/Task tool and let it start working.
- While it is running (or idle), send it a follow-up message (
SendMessage/ mailbox). - Inspect the subagent transcript.
Expected: the follow-up message is appended to the agent's existing context; skills were already injected at spawn.
Actual: the frontmatter skill bodies (<command-message> blocks) are re-rendered in full, byte-identical to the spawn-time injection, and SubagentStart hooks fire again.
A fresh Agent() call is fine — new instance, injection expected. The issue is specifically messages to an existing agent instance.
Evidence (from our fleet telemetry)
- One agent (3 frontmatter skills, ~68 KB combined) received a mid-task follow-up ("approval granted, continue") and its transcript shows the complete skill set injected twice: our SubagentStart-side skill logger recorded identical
source:"frontmatter"entry sets for the same agent instance at2026-07-21T12:22:17Zand2026-07-21T12:49:56Z. Transcript diff confirmed the re-rendered blocks were byte-identical. - Our own SubagentStart hook output is small (~1.2–2.4 KB); after we added an inject-once guard keyed on
agent_id, the second fire emitted 0 B — verified on disk (fire 1 = 2,379 B, fire 2 = 0 B). The remaining ~68 KB per nudge comes from the native frontmatter-skills render, which hooks cannot suppress.
Why it matters
- Uncounted recurring cost: every coordination message to a live subagent silently re-pays the full skill preload. Multi-nudge orchestration patterns (send context → send approval → send correction) multiply it.
- It works against the feature: the practical mitigation is to move large skills out of frontmatter
skills:into on-demand loading — i.e. abandoning the v2.1.183 preload feature for any skill big enough to matter. - No contract to design against: the docs describe frontmatter
skills:injection at spawn but say nothing about behavior on subsequent messages (re-render vs. retain, hook firing cadence, any once-only mechanism). We had to establish all of the above empirically.
Ask
- Document the contract for SubagentStart firing and frontmatter-skill rendering on messages to an existing agent instance.
- Preferably, make frontmatter skill bodies inject once per agent instance (or offer an opt-in flag for that behavior).
- Failing that, expose spawn-vs-resume to hooks (e.g. a
resume: truefield in the SubagentStart payload) so harnesses can at least reason about and meter the difference.
Happy to provide transcripts or run instrumented repros on request.