Messaging a running subagent re-injects its full frontmatter skills: bodies and re-fires SubagentStart hooks — undocumented and token-costly

Status Open
Reported on v2.1.215
Maintainer reply None cached
Activity 0 comments · opened Jul 23, 2026

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/*.md with frontmatter skills: (native context injection since v2.1.183).

Reproduction

  1. Define a subagent in .claude/agents/foo.md with frontmatter skills: listing skills whose bodies total tens of KB.
  2. Optionally register a SubagentStart hook that emits any marker text.
  3. Spawn the agent with the Agent/Task tool and let it start working.
  4. While it is running (or idle), send it a follow-up message (SendMessage / mailbox).
  5. 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 at 2026-07-21T12:22:17Z and 2026-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

  1. Document the contract for SubagentStart firing and frontmatter-skill rendering on messages to an existing agent instance.
  2. Preferably, make frontmatter skill bodies inject once per agent instance (or offer an opt-in flag for that behavior).
  3. Failing that, expose spawn-vs-resume to hooks (e.g. a resume: true field 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.

View original on GitHub ↗