Skip to content

fix(context): reset context length after a /compact boundary#524

Merged
sirmalloc merged 2 commits into
sirmalloc:mainfrom
wmaurer:fix/context-length-after-compaction
Jul 25, 2026
Merged

fix(context): reset context length after a /compact boundary#524
sirmalloc merged 2 commits into
sirmalloc:mainfrom
wmaurer:fix/context-length-after-compaction

Conversation

@wmaurer

@wmaurer wmaurer commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Problem

Context-length / context-percentage widgets stay stuck at the pre-compaction token count after a /compact, until the next assistant turn — the bug reported in #92.

Right after /compact, Claude Code sets context_window.current_usage to null, so the context widgets fall back to transcript parsing. getTokenMetrics() then derives context length from the most recent usage entry — the now-defunct pre-compaction turn — so the displayed size never drops.

When #92 was closed, the reason given was that /compact "doesn't send anything back that gets logged to the jsonl." That's no longer true: Claude Code writes a { type: 'system', subtype: 'compact_boundary', compactMetadata: { preTokens, postTokens } } record on every compaction — the same marker the Compaction Counter widget already parses.

Fix

getTokenMetrics() is now compaction-aware. Usage entries before the most recent compact_boundary describe a context that no longer exists, so they no longer drive context length. Resolution order for contextLength:

  1. the first main-chain turn after the boundary (once another message is sent), else
  2. the boundary's compactMetadata.postTokens, else
  3. 0 (older transcripts without postTokens) — never the stale pre-compaction number.

Session token totals (inputTokens, outputTokens, cachedTokens, totalTokens) stay cumulative — only contextLength resets. Behavior with no compaction is unchanged.

Boundary detection is reused from compaction.ts (isCompactBoundary is now exported, plus a small getCompactBoundaryPostTokens helper), so there's no duplicated parsing and no second pass over the transcript.

Tests

Added 3 cases to jsonl-metrics.test.ts:

  • post-compaction size taken from postTokens before the next turn,
  • first post-boundary turn supersedes the postTokens estimate,
  • marker without postTokens + no following turn → 0 (no stale leak).

bun test (full suite) and bun run lint (tsc + eslint, --max-warnings=0) pass.

Before / after

Post-/compact, boundary postTokens = 18000, pre-compaction context ~235k:

status line
before ... 235.0k (23.5%)
after ... 18.0k (1.8%)

Wayne Maurer and others added 2 commits July 23, 2026 15:46
…dary

- Stop context length sticking at the stale pre-compaction size after a
  compaction: usage entries before the most recent compact_boundary no
  longer drive contextLength
- Prefer the first main-chain turn after the boundary, fall back to the
  boundary's compactMetadata.postTokens, otherwise 0
- Export isCompactBoundary and add getCompactBoundaryPostTokens helper
  in compaction.ts
- Add tests covering boundary with postTokens, boundary followed by a
  new turn, and older-format boundary without postTokens
@sirmalloc
sirmalloc merged commit 69daa73 into sirmalloc:main Jul 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants