[BUG] After compaction, the preserved tail keeps a superseded CLAUDE.md "instructions" attachment, and a second full copy is emitted right after the compact
Environment
- Claude Code 2.1.280. The 2.1.281–2.1.283 changelogs have no related entry. The 2.1.281 fix for
--add-dirinstruction files being sent twice does not apply here: our--add-dirdirectory is outside the working directory, and the duplicated file is the project-rootCLAUDE.md. - Linux aarch64,
claude-opus-5-5(1M window), headlessclaude -p --resume, subscription auth. - A project-root
CLAUDE.mdof about 330–360K characters that changes several times a day.
What's wrong
When a session auto-compacts, the messages after the compaction anchor are kept verbatim (the "preserved tail"). If that tail contains an instructions attachment, i.e. an older version of a CLAUDE.md that has changed since, the old copy stays in the compacted context next to the post-compact instructions snapshot.
- In 10 of 18 auto-compactions we inspected, the superseded attachment was still in the preserved tail. Each of those sessions kept 1–3 extra full copies of the root
CLAUDE.mdafter compaction. - In 3 of 18, the post-compact instructions snapshot was placed before the preserved tail. The stale "changed" attachment in the tail therefore became the last version the session saw. On the next turn the file was treated as changed again, and a second full attachment was emitted right after the compaction.
Example from one transcript: a request at 905,084 tokens, then a compact_boundary with trigger: auto and preTokens: 990,705, followed by two instructions attachments of 363,203 and 329,080 characters (rendered field), both versions of the root CLAUDE.md.
We matched each attachment to a file version by exact byte length: the stored attachment is always the UTF-8 size of that version plus 2 bytes.
Why it matters
- Compaction frees less space than it should, and the next overflow comes sooner. This is the same mechanism as #85489 (refill blamed on "a file or tool output") and #82131 (thrashing).
- Cost: the extra copies are written to and read from the cache on every following turn. They cost us about US$ 51–60 over ~2.1 days across our sessions, for text the model already had in its current version.
- Two versions of the same instruction file sit side by side in context. When a rule was removed or changed, the old wording is still there.
Expected
- When the preserved tail is built, drop
instructionsattachments that a newer version or the post-compact snapshot has superseded. At minimum, keep only the newest version of each file. - Place the post-compact instructions snapshot after the preserved tail, or use it as the change-detection baseline. A stale copy in the tail should never make the file look "changed" again.
- Optionally, record in
compact_metadatahow many tokens of instructions were re-attached (see #87602).
Steps to reproduce
- Create a project with a large root
CLAUDE.md(a few hundred KB is enough on a 1M window; for a 200K window, scale it down proportionally). - Run a long session until it is close to the auto-compact threshold.
- Edit
CLAUDE.md(append a line), then send a turn. Aninstructionsattachment with the new version appears. - Keep going until auto-compaction fires while that attachment is still among the most recent messages.
- In the transcript JSONL after the
compact_boundary, count theattachmententries of typeinstructionsfor that file. You will find the old copy in the preserved tail next to the new snapshot, and sometimes a second attachment on the first turn after the compact.
Related
#92434 (instructions attachment not counted before the auto-compact check), #85489, #82131, #70459 (preserved tail keeps a large verbatim block), #96820 (re-attached content makes post-compact context larger).
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗