Summary
Double compaction still occurs on builds containing the #9282 fix (PR #13514). The fix moved appendCacheTtlTimestamp() from before prompt to after prompt+compaction, but this only changed timing, not the fundamental issue: the cache-ttl custom entry still inserts between the compaction entry and the next guard check, allowing prepareCompaction() to trigger a second compaction.
Environment
Reproduction
Observed on a live session (d2160dd5) with 6 compaction entries = 3 double pairs. Pattern is 100% consistent:
Evidence: 3 double compaction pairs (all on 2026-02-27, post-fix build)
Pair 1:
L217: COMPACTION 05:03:22.667 tokensBefore=180,013 summary=7,810 chars
L218: CACHE-TTL 05:03:22.668 (+1ms, type: "custom")
L219: COMPACTION 05:05:32.714 tokensBefore=1,953 summary=7,935 chars (+125)
Pair 2:
L544: COMPACTION 06:58:44.362 tokensBefore=180,650 summary=19,060 chars
L545: CACHE-TTL 06:58:44.392 (+30ms, type: "custom")
L546: COMPACTION 06:58:44.502 tokensBefore=4,765 summary=19,185 chars (+125)
Pair 3:
L661: COMPACTION 07:40:15.449 tokensBefore=181,521 summary=15,190 chars
L662: CACHE-TTL 07:40:15.490 (+41ms, type: "custom")
L663: COMPACTION 07:42:22.825 tokensBefore=3,798 summary=15,315 chars (+125)
Key observations
-
All second compactions fire on tiny contexts (1,953 / 4,765 / 3,798 tokens) — completely unnecessary. First compactions are legitimate (~180K tokens near context limit).
-
The +125 chars delta is identical every time — it's <workspace-critical-rules> from compaction-safeguard.ts L349-351 being appended a second time:
\n\n<workspace-critical-rules>\n## Session Startup\n...\n## Red Lines\n...\n</workspace-critical-rules>
-
Pair 2 completed in 140ms — the LLM "summarized" 4,765 tokens of mostly-existing-summary in a trivial pass.
-
Cache-TTL entry is always the sole entry between compaction pairs — the type: "custom" entry breaks the guard every time.
Root cause analysis
The #9282 fix (PR #13514) moved appendCacheTtlTimestamp() from before the prompt to after prompt+compaction completes. But the guard logic in prepareCompaction() checks whether the last session entry has type === "compaction":
Before fix: cache-ttl → prompt → compaction → [next cycle: last=compaction ✅, but cache-ttl was BEFORE]
After fix: prompt → compaction → cache-ttl → [next cycle: last=custom ❌ → double compaction]
The fix changed when cache-ttl is inserted but didn't change the fundamental issue: any type: "custom" entry between two compaction checks allows prepareCompaction() to fire again.
Impact
| Impact |
Severity |
| Wasted LLM API call per compaction (~2 min, real tokens billed) |
Medium |
compactionCount inflated 2x (6 recorded = 3 real) |
High — affects memoryFlushCompactionCount guard |
workspace-critical-rules duplicated in summary |
Low |
| Compaction on <5K tokens may degrade summary quality |
Medium |
Suggested fix directions
Option A (minimal): In the guard check, look at the last N entries (e.g., 2-3) instead of only the last entry. If any recent entry is type: "compaction", skip re-compaction.
Option B (structural): Don't emit cache-ttl as a standalone session entry. Instead, embed the timestamp in the compaction entry's metadata or in the next assistant message's metadata.
Option C (safety net): Add a tokensBefore floor check — if tokensBefore < threshold (e.g., 10% of context window), skip compaction entirely since there's nothing meaningful to summarize.
Related
Summary
Double compaction still occurs on builds containing the #9282 fix (PR #13514). The fix moved
appendCacheTtlTimestamp()from before prompt to after prompt+compaction, but this only changed timing, not the fundamental issue: thecache-ttlcustom entry still inserts between the compaction entry and the next guard check, allowingprepareCompaction()to trigger a second compaction.Environment
2026.2.24(built 2026-02-25, commitd4708733a)contextPruning.mode: "cache-ttl"dcb921944(fix for fix: duplicate compaction triggered by stale usage stats and cache-ttl custom entry bypass #9282) confirmed present in buildReproduction
Observed on a live session (
d2160dd5) with 6 compaction entries = 3 double pairs. Pattern is 100% consistent:Evidence: 3 double compaction pairs (all on 2026-02-27, post-fix build)
Key observations
All second compactions fire on tiny contexts (1,953 / 4,765 / 3,798 tokens) — completely unnecessary. First compactions are legitimate (~180K tokens near context limit).
The +125 chars delta is identical every time — it's
<workspace-critical-rules>fromcompaction-safeguard.tsL349-351 being appended a second time:Pair 2 completed in 140ms — the LLM "summarized" 4,765 tokens of mostly-existing-summary in a trivial pass.
Cache-TTL entry is always the sole entry between compaction pairs — the
type: "custom"entry breaks the guard every time.Root cause analysis
The #9282 fix (PR #13514) moved
appendCacheTtlTimestamp()from before the prompt to after prompt+compaction completes. But the guard logic inprepareCompaction()checks whether the last session entry hastype === "compaction":The fix changed when cache-ttl is inserted but didn't change the fundamental issue: any
type: "custom"entry between two compaction checks allowsprepareCompaction()to fire again.Impact
compactionCountinflated 2x (6 recorded = 3 real)memoryFlushCompactionCountguardworkspace-critical-rulesduplicated in summarySuggested fix directions
Option A (minimal): In the guard check, look at the last N entries (e.g., 2-3) instead of only the last entry. If any recent entry is
type: "compaction", skip re-compaction.Option B (structural): Don't emit cache-ttl as a standalone session entry. Instead, embed the timestamp in the compaction entry's metadata or in the next assistant message's metadata.
Option C (safety net): Add a
tokensBeforefloor check — iftokensBefore < threshold(e.g., 10% of context window), skip compaction entirely since there's nothing meaningful to summarize.Related