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 4 comments · 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 ↗

3 Comments

swc200git · 2 months ago

Possibly the same thing, possibly a different trigger for the same symptom: called the same skill four times in one long session (no manual /compact, but the session was long enough that automatic compaction may have happened between calls without that being visible from the transcript). Each call reprinted the entire multi-hundred-line instruction set, even though the output explicitly said "the skill instructions were previously loaded." If this turns out to reproduce without any compaction having occurred, that's a distinct repro worth its own issue rather than folding in here.

kbr- · 18 days ago

Another data point, from Claude Code 2.1.283.

A workflow that tells the agent to re-invoke its skills after every compaction ends up paying for each skill twice: the truncated copy the harness re-attaches is useless, so the agent loads the full skill again on top of it.

  • One 27.4 KB skill (~7k tokens) came back cut at the 5k-token cap, ending mid-section with [... skill content truncated for compaction; ...]. The whole last section was gone, and it held the most operational rules.
  • The agent had to load the skill again anyway, so the re-attached copy was ~5k tokens of pure duplication.

Neither workaround is available today: there's no way to get the full body re-attached, and no way to turn re-attachment off for a skill that the workflow reloads by itself.

A concrete ask: a skill frontmatter field (or setting), e.g. compaction: full | pointer | none:

  • full: re-attach the whole body, counted against the aggregate budget, for short skills whose guidelines must survive compaction;
  • pointer: re-attach only the name and path, with a note to load it on demand (the deferred-tools pattern this issue proposes), which I'd suggest as the default;
  • none: don't re-attach at all, for workflows that reload their skills explicitly.

Even without the field, pointer as the default would remove both the cost and the risk of the model trusting a copy that looks complete but isn't.

---

Issue definitely not stale.

StefanHH2 · 15 days ago

Another measurement, from the cost side: it includes the cache rebuild that the re-injection rides on.

Setup. Claude Code CLI, claude-opus-5-5, 1-hour cache TTL. One 164-request session with two auto-compactions. Four Spec Kit skills (speckit-specify, -plan, -tasks, -implement) were invoked in sequence, and each was complete before the next began. Numbers come from the session transcript (per-request usage), priced at list price.

| | compaction 1 | compaction 2 |
|---|---:|---:|
| context before → after | 158.9k → 91.0k | 166.0k → 88.9k |
| cache written by the first request after (rebuild) | 61.0k tokens, 0.50 USD | 58.9k tokens, 0.48 USD |
| invoked_skills reminder re-attached | 53.1 KB (≈16k tokens) | 53.1 KB (≈16k tokens) |
| deferred_tools_delta re-attached | 42.4 KB | 42.4 KB |

  • The re-attached skill bodies are about a quarter of each rebuild: ≈16k tokens × 8 USD/M ≈ 0.13 USD per compaction. After that they are carried cached, at ≈0.003 USD per request.
  • The block always includes speckit-specify (19.6 KB). That skill finished about an hour before the first compaction and was never needed again.
  • Cost per request is flat across the compactions: 0.063 / 0.059 / 0.063 USD per request for the three segments. The expensive event is the rebuild itself. A ~40-line task that spans a compaction cost 0.99 USD, and 0.50 USD of that was the rebuild.

Ask (same as above): re-attach only the skill still in progress, or a pointer such as name + path instead of the body. Every completed skill replayed in full raises the price of every compaction.

Showing cached comments. Read the full discussion on GitHub ↗