Post-compaction skill re-injection costs ~4x the compaction summary: full bodies, byte-truncated, cumulative

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 1 comment · opened Jul 29, 2026

Summary

After a compaction, the <system-reminder> block listing "skills invoked EARLIER in this session" re-injects each skill's full body, byte-truncated. In a session with five invoked skills this cost roughly 4× more context than the compaction summary itself — the summary being the only artifact that carries the session's actual work.

The prior reports on this block (#61848, #50724) are about correctness — phantom invocations and stale ARGUMENTS leaking as live input. This one is about cost, which is a separate axis and, unlike those, is fully deterministic and easy to measure.

Measured — one real session

Claude Code CLI 2.1.220, Opus 5, one manual /compact after five skills had been invoked across the session.

| Block | Size | Carries |
|---|---|---|
| Compaction summary | ~4k tok (est.) | The entire session's work |
| Five re-injected skill bodies | ~16k tok (est., post-truncation) | Five completed procedures |
| Same five skills, untruncated on disk | 107,426 bytes / ~27k tok (measured) | — |

Per-skill source sizes, measured with wc -c:

25,472  a research/synthesis skill
32,579  a code-walk skill
18,832  a debug/fix skill
26,628  a compliance-advisory skill
 3,915  a self-compaction skill
-------
107,426 bytes  (~27k tokens at 4 B/tok)

Three compounding problems

1. Full bodies, not pointers. The block pastes each skill whole. Contrast the deferred-tool mechanism in the same prompt, which solves exactly this: 51 tools are listed as names only (~400 tokens) and their schemas are fetched on demand via ToolSearch. Skills get no equivalent — and unlike a tool schema, a skill body for already-completed work is rarely needed again at all.

2. Truncation is a byte offset, not a semantic boundary. Four of the five arrived cut mid-structure — one mid-table, one mid-numbered-step, one mid-section — each ending in [... skill content truncated for compaction; use Read on the skill path if you need the full text].

The cut keeps the head (title, overview, setup) and discards the tail (operational detail, the actual procedure). That is backwards for a block whose stated purpose is "so you remain aware of their guidelines": the guidelines are in the part thrown away. The result is a body that is simultaneously too large to be cheap and too incomplete to be trusted — and, because it looks whole, it invites the model to rely on it rather than re-Read the file.

3. Cumulative and session-scoped. The set only grows. A second compaction in the same session re-injects the same five bodies again at full price. There is no decay and no dedup against the summary — and the summary frequently restates the same facts the skill args already carry.

There is also duplication within a single body: one skill embeds its invocation arguments in its header and then a classification step re-quotes them verbatim, so a ~900-token argument block is held twice inside one already-truncated skill.

Expected

The post-compaction skill reminder should cost proportional to its value. For skills whose invocation has already completed, a pointer is sufficient:

### Skills invoked earlier this session (not active; Read the path if you need one)
- rr — projectSettings:rr — .claude/skills/rr/SKILL.md
- walker — projectSettings:wave:walker — .claude/commands/wave/walker.md
...

Actual

Five full (truncated) bodies totaling ~4× the compaction summary, describing work that is finished, cut at arbitrary offsets that preferentially discard the operational content.

Proposed fix, ranked

  1. Replace bodies with pointer lines. A truncated body is already a pointer — just an expensive one that misrepresents itself as complete. The truncation notice even names the remedy (use Read on the skill path), which is precisely what a one-line pointer would say for ~1% of the tokens.
  2. If bodies must stay, truncate semantically — cut at section boundaries, and prefer the operational tail over the overview head.
  3. Deduplicate the ARGUMENTS block within a body (this also shrinks the surface for #50724's stale-args bug).
  4. Surface the cost. Report the re-injection size somewhere the user can see. Right now the arithmetic that would drive "compact vs. start fresh" is invisible at exactly the moment that decision is made.

Note that (1) also strictly reduces the blast radius of #61848 and #50724: a pointer line cannot be mistaken for a live task, and carries no ARGUMENTS payload to leak.

Repro

  1. In one session, invoke 4–5 non-trivial skills (≥15KB each).
  2. Run /compact.
  3. Inspect the resulting context: each skill reappears as a full, byte-truncated body under "invoked EARLIER in this session".
  4. Compare the total against the compaction summary.

Deterministic — no timing or plugin dependency.

Related

  • #61848 — phantom skill invocation in the same block (closed, not planned)
  • #50724 — stale skill ARGUMENTS re-surfaced as fresh prompt (closed, not planned)

Same subsystem, third failure mode.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗