[BUG] Frontmatter-preloaded agent skills re-injected in full on every SendMessage resume (linear duplicate context growth)
[BUG] Frontmatter-preloaded agent skills are re-injected in full on every SendMessage resume (linear duplicate context growth)
Summary
When a custom agent (.claude/agents/*.md or plugin agent) declares skills: in its frontmatter, the full text of each listed skill is injected into the subagent's context at spawn — as expected. However, every subsequent SendMessage resume of that subagent re-injects the entire preload set again, verbatim. Copies grow linearly: N resumes → N+1 identical copies of every preloaded skill in the agent's context.
For an agent with 4 preloaded skills (~19k chars / ~5k tokens per set), a 10-resume conversation carries ~50k tokens of byte-identical duplicate skill text.
Environment
- Claude Code: 2.1.206
- Platform: macOS 26.5.1 (darwin), CLI
- Agent type: plugin-defined subagent with frontmatter:
```yaml
skills:
- executing-plans
- conventional-commit
- test-driven-development
- verification-before-completion
```
Reproduction
- Define an agent with 2+ skills in its frontmatter
skills:list. - Spawn it via the
Agenttool with any prompt. - Resume it twice via
SendMessagewith a trivial message each time (e.g. "Reply with the single word ack. No tools, no skills."). - Count skill injections in the subagent transcript (
<session-dir>/tasks/<agentId>.output):
grep -o '<command-name>[a-z-]*</command-name>' tasks/<agentId>.output | sort | uniq -c
Observed results (actual transcript data)
Injection counts per preloaded skill:
| State | Copies of EACH preloaded skill |
|---|---|
| Fresh spawn (0 resumes) | 1 |
| After resume 1 | 2 |
| After resume 2 | 3 |
Injection timestamps from the transcript show one full batch of 4 skills per event — spawn and each resume:
2026-07-10T08:58:22Z executing-plans, conventional-commit, test-driven-development, verification-before-completion (spawn)
2026-07-10T09:12:50Z executing-plans, conventional-commit, test-driven-development, verification-before-completion (resume 1)
2026-07-10T09:13:21Z executing-plans, conventional-commit, test-driven-development, verification-before-completion (resume 2)
The duplicates are byte-identical: all 3 copies of the test-driven-development block are 9,892 chars with the same sha256 (729e0fa6…). One full preload set is ~19k chars (~5k tokens).
Each injection arrives as an isMeta: true user message with the standard skill wrapper:
<command-message>test-driven-development</command-message>
<command-name>test-driven-development</command-name>
<skill-format>true</skill-format>
Base directory for this skill: …/skills/test-driven-development
# Test-Driven Development (TDD)
…full body…
Cumulative subagent token usage corroborates: 35,659 → 43,247 → 51,387 tokens across the two resumes — ~8k tokens added per resume even though the agent's entire output each time was the single word "ack" with zero tool calls.
Expected
Preloaded skills are injected once at spawn. A SendMessage resume continues from the existing transcript (which already contains the skill text) without re-injecting it — same as how the rest of the transcript is not duplicated on resume.
Impact
- Linear context bloat per resume for any agent using frontmatter skill preloads — directly multiplies token cost and eats the context window of long-lived agents.
- Punishes exactly the recommended pattern: the harness suggests continuing agents via
SendMessageto preserve context, but each continuation re-pays the full preload. - Compounds with larger
skills:lists — cost scales as (skills total size) × (resume count).
Workarounds
- Keep frontmatter
skills:lists minimal. - Prefer fresh short-lived agent dispatches over long
SendMessagechains.
Possibly related (different mechanisms, for triage cross-ref)
- #25994 — skills doubled after compaction (main session; closed)
- #73280 — compaction summarizes skill content instead of clearing it
- #67345 / #75054 / #67794 — other state lost/mishandled on
SendMessageresume (model override), suggesting resume path rebuilds spawn-time state instead of restoring it
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗