advisor() tool inflates reported input tokens by forwarding full transcript, triggering premature auto-compaction on extended context models
Summary
When advisor() is called, the full conversation transcript is forwarded to a second model (currently claude-opus-4-7). The token usage from both the main executor and the advisor sub-inference are summed in the top-level usage fields. If Claude Code's auto-compaction logic uses these summed totals, the advisor call effectively doubles the apparent context usage, triggering compaction when the main model's actual context is only ~50% full.
Reproduction
- Start a Claude Code session with
claude-opus-4-6[1m](1M context) - Work normally until the main context reaches ~400K-500K input tokens
- Call
advisor() - Observe: auto-compaction fires immediately after the advisor call returns
Evidence from session JSONL
Session ID: c3a29188-290f-4af2-be48-8f6fb6929111
Model: claude-opus-4-6[1m]
Project: CriticalSkip
Token progression at the compaction boundary
| JSONL line | Total reported input | Notes |
|-----------|---------------------|-------|
| 1972 | 513,354 | Normal turn, main context only |
| 1979 | 1,028,789 | advisor() called — reported total doubled |
| 1994 | — | "Conversation compacted" system message |
| 2012 | 35,693 | Post-compaction context (wiped to ~36K) |
Iteration breakdown for the advisor turn (line 1979)
{
"input_tokens": 4,
"cache_creation_input_tokens": 1731,
"cache_read_input_tokens": 1027054,
"iterations": [
{
"type": "message",
"input_tokens": 3,
"cache_read_input_tokens": 513353,
"cache_creation_input_tokens": 348,
"output_tokens": 35
},
{
"type": "advisor_message",
"model": "claude-opus-4-7",
"input_tokens": 701354,
"cache_read_input_tokens": 0,
"cache_creation_input_tokens": 0,
"output_tokens": 5672
},
{
"type": "message",
"input_tokens": 1,
"cache_read_input_tokens": 513701,
"cache_creation_input_tokens": 1383,
"output_tokens": 544
}
]
}
Key observation: The main model's actual context was 513-515K tokens (iterations 1 and 3). The advisor sub-inference consumed 701K tokens (iteration 2 — the full transcript forwarded uncached). The top-level cache_read_input_tokens reports 1,027,054 — the sum across all iterations — making it appear the context is at 1M when only half is actually used by the executor.
The math
- Main executor context: ~513K tokens (well within 1M window)
- Advisor receives full transcript: ~701K tokens (separate model, separate inference)
- Reported total: ~1,028K tokens (sum of both)
- Auto-compaction threshold: likely ~95% of 1M ≈ 950K
- Result: compaction fires at 513K actual context because 1,028K > threshold
Expected behavior
Advisor sub-inference tokens should not count toward the auto-compaction threshold. The advisor is a separate model call with its own context window. The executor's context at 513K is well within the 1M budget and should not trigger compaction.
Actual behavior
The summed total (executor + advisor) exceeds the compaction threshold, and auto-compaction fires immediately. The session loses ~500K tokens of working context unnecessarily.
Impact
- On extended context models (1M): Any advisor call past ~400K main context will trigger compaction, since
400K main + ~550K advisor = ~950K ≈ threshold - Effectively halves usable context: The 1M context window becomes ~400-450K when advisor is used, because the remaining ~550K is reserved for the advisor's copy of the transcript
- Inconsistent: Early-session advisor calls work fine. Late-session calls compact. Users experience this as random/intermittent compaction
- Counterproductive: The advisor is most valuable late in a session (complex decisions with full context), but that's exactly when it triggers compaction and destroys the context it was supposed to help with
Workaround
Setting CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=98 and/or using a PreCompact hook that blocks auto-compaction:
{
"hooks": {
"PreCompact": [{
"hooks": [{
"type": "command",
"command": "bash -c 'INPUT=$(cat); TRIGGER=$(echo \"$INPUT\" | python3 -c \"import sys,json; print(json.load(sys.stdin).get(\\\"trigger\\\",\\\"unknown\\\"))\" 2>/dev/null); [ \"$TRIGGER\" = \"auto\" ] && echo \"{\\\"decision\\\":\\\"block\\\"}\" && exit 2; exit 0'",
"timeout": 3
}]
}]
}
}
This blocks all auto-compaction and relies on manual /compact. Not ideal but prevents the advisor from triggering premature compaction.
Related issues
- #34332 — Opus 4.6 (1M context): autocompact triggers at ~76K tokens
- #50204 — Auto-compact triggers prematurely with extended context models
- #15377 — Conversations compacting prematurely at ~65% token capacity
- #49994 — Sessions using advisor() become permanently unrecoverable (expired encrypted payloads)
- #42647 — High token burn due to redundant context resubmission in compaction pipeline
Suggested fix
When calculating context usage for the auto-compaction threshold, use only the executor's input_tokens + cache_read_input_tokens + cache_creation_input_tokens from type: "message" iterations. Exclude type: "advisor_message" iterations from the calculation. The advisor tool documentation already states that "top-level max_tokens applies to executor output only" and that "the advisor's tokens do not draw from any task budget applied to the executor" — the compaction logic should follow the same principle.
Environment
- Claude Code version: 2.1.111
- Model: claude-opus-4-6[1m]
- Advisor model: claude-opus-4-7
- Platform: Windows 10 Pro
- Settings:
advisorModel: "opus",effortLevel: "medium"
18 Comments
Ditto. I have this exact same issue and even filed an issue (#55702) that turned out to be a duplicate of this one. I'm using Linux with Opus 4.6 [1M] as my main model and Opus 4.7 as the advisor model.
I have auto-compaction disabled. My workaround has been to
/rewindto right before the advisor call and ask Claude to generate a session handoff file with the next instruction saying to call the advisor. Then I/clearand run@SESSION_HANDOFF.md.This bug still occurs on v2.1.126.
Yesterday I started a new session on v2.1.126 with Opus 4.7 and gave it a single complex prompt using a session handoff file. The context window limit was reached before Claude could completely finish my prompt, and it happened because Claude invoked the advisor at around 480K context tokens which triggered this bug; thus, the context size doubled and my prompt died due to hitting the context limit.
Since my session was literally just a single prompt, I couldn't
/rewindback before the advisor call to salvage my session.Please update the tags to specify that this bug isn't limited to Windows, as I experience this bug in Linux.
The bug is 100% reproducible. It really diminishes the value of the advisor feature.
This issue still occurs on v2.1.140. Please, kindly prioritize fixing it as it significantly limits the advisor's potential.
still happening...
Still happens on v2.1.148, and it is still 100% reproducible. I even got a weird one today where 3 back-to-back Advisor calls caused my session to hit the 1M context limit even though I started with 25% of the context window full. So multiple consecutive Advisor calls can bust a session even when you're well below 50% of context filled.
More info — independently reproduced on
claude-opus-4-7[1m], with two facets worth adding.Hit this on Claude Code 2.1.150 / macOS /
claude-opus-4-7[1m]. A singleadvisor()call took the statusline from ~406K →1170k/1000k (117%),+782kin one turn. Iteration-level data confirms your diagnosis exactly, and adds (1) a timeout-driven ×3 and (2) confirmation it's display-only — the prompt cache and the executor's real context are untouched (the #17959 symptom).Same accounting, confirmed
The advisor turn's
usage:381449 + 387405 + 387405 = 1156259= the top-levelcache_readexactly. So the top-levelcache_readis Σcache_readover the executor (type:"message") sub-iterations, and eachadvisor()invocation adds one executor re-read of the same cached context. General rule:The advisor's transcript forward is the separate
advisor_message(input_tokens=395834, uncached) and is not in the top-levelcache_read. Control from the same session — a successful, single advisor call on a ~113K context reportedcache_read = 113080 + 114484 = 227564→ ×2, matching your 513K→1028K.Facet 1 —
execution_time_exceeded→ auto-retry → ×3 (not ×2)My turn has two
server_tool_use{name:"advisor"}blocks. The first returned:…on a ~385K transcript (the reviewer can't finish within its server-side budget), so it auto-retried. Two invocations → three executor
messageiterations → ×3 (1,156,259), not ×2. The timed-out attempt left no billedadvisor_messageiteration but cost ~8 min wall-clock and forced the extra re-read at iteration[1]. So on large contexts the inflation multiplier can exceed 2.Facet 2 — the inflation is display-only; cache and real context are untouched
The 117% does not reflect real context growth. The next turn read
cache_read=387405withcache_creation=7167— a normal incremental, so the prompt cache stayed fully intact and the executor's real context never left ~385K. The inflated figure is a pure accounting artifact of the summedcache_read, not a real approach to the window limit — exactly the #17959 symptom (context_window.used_percentagenot matching the real/internal figure).The harm this issue raises is that the same summed figure also feeds the auto-compaction trigger, so a complete fix needs both the statusline and the auto-compaction trigger to use the executor's real context and exclude server-tool sub-iteration re-reads of already-cached content.
Honest cost (so the multiplier isn't misread)
1,156,259is the same ~385K counted 3× atcache_readrate (~0.1×) — cheap (~115K full-rate-equiv).advisor_messageforward: ~396K full-rate input + 6K output, once.Environment: Claude Code
2.1.150,claude-opus-4-7[1m], Anthropic API, macOS, Ghostty/zsh.Still happens on v2.1.156. I ran into it with Opus 4.8 (1M) today.
The advisor() double-count is nasty because the
usagefields in the JSONL show the inflated sum, which is what CC's auto-compact trigger reads — so from CC's perspective you've "hit 1M" even though the actual transcript is half that.cozempic (github.com/Ruya-AI/cozempic) helps in two ways here, though I want to be honest about what it can and can't do: it doesn't fix the double-counting in CC's token reporting. What it can do:
cozempic current --diagnosereads the raw per-message token counts directly from the JSONL and gives you the real context footprint independent of the runningusagetotal. If you're seeing 1M reported but the actual accumulated transcript is ~500K, the diagnose output will show the discrepancy — which at minimum tells you whether you're hitting a real limit or a counting bug.pip install cozempic/pipx install cozempic. Given this bug has been reproducing on 4.6, 4.7, and 4.8 across multiple people — would be curious whether the diagnose numbers match what you're seeing or surface something different.Still not fixed.
An abysmal bug and still an issue 2 months later!
Still occurs on v2.1.196.
Same root cause without
advisor()— plainmessagecontinuation/retry iterations also trigger itI hit the same premature auto-compaction mechanism described here, but with no advisor call anywhere in the session — every entry in the
iterationsarrays is"type": "message". This suggests the fix belongs in the compaction accounting (it should use the actual prompt size of the last/largest single call, not the summed top-levelusage), not in the advisor tool specifically.Environment
claude-sonnet-5(no 1M variant, no advisor, no custom compaction overrides)a398982a-2ad8-48a5-9560-95c2b60fc539— 44 minutes, 3 auto-compactionsEvidence from the session JSONL
Each
compact_boundary'spreTokensmatches the summed multi-iteration usage of the preceding assistant turn, roughly 2x the real context of the largest single API call:| Compaction |
preTokens| Largest single iteration (real context) | Inflation ||---|---|---|---|
| #1 (auto) | 269,302 | ~131k (
in=709, cache_read=130,705) | ~2.0x || #2 (auto) | 167,697 | ~65k (
in=55,291, cache_read=9,629— cache-miss full resend) | ~2.6x || #3 (auto) | 171,976 | ~87k (
in=640, cache_read=84,035) | ~2.0x |Iteration breakdown for the turn preceding compaction #2 (top-level usage is the sum of all three calls):
The third iteration is a cache-miss retry that re-sent the conversation uncached (~55k real context), yet the summed total (~166k) is what the compaction trigger saw. Compaction #2 fired when the window was roughly a third full.
Impact: the three compactions burned ~8.5 minutes of a 44-minute session (
durationMs263,741 / 109,517 / 136,274) and repeatedly discarded working context that was nowhere near the limit. Because retries/continuations can happen on any model, this isn't limited to extended-context models or advisor users.Corroborating with an 8-day quantified dataset — #84738 pins this mechanism with transcript-level evidence: a turn that invokes the advisor reports usage summed across
usage.iterations— two full-context message iterations, so top-level usage comes out at ~2× real context (e.g. cache reads 515,122 + 515,905 → 1,031,027 reported) — and auto-compact eligibility reads the rolled-up figure. Quantified over 8 days of transcripts on one machine: every subagent auto-compaction in the window fired on the turn immediately after an inflated advisor rollup, at real contexts of 310–517K, average (preTokens − real context) ≈ 350–390K. Subagent seats consulting the advisor at checkpoints are hit hardest.Confirming the mechanism on 2.1.232 (latest as of 2026-08-13) at the binary level, with account-wide numbers and a tested, end-to-end-verified client-side workaround (PreCompact hook).
Where the false number comes from
The context figure is derived from the last assistant message's top-level
usageasinput_tokens + cache_creation_input_tokens + cache_read_input_tokens. For a turn containing a server-side tool round trip (the advisor), that top-level object sums every client inference pass, and both the status-line percentage and the auto-compact eligibility check read this same rolled-up sum. So the meter shows ~2× real occupancy with one advisor call, ~3× with two (matches the (N+1)× arithmetic reported above).The transcript already carries the correct figure:
usage.iterationsholds the per-pass breakdown, where client passes aretype: "message"(2 uncached input tokens each — hence top-levelinput_tokens= 4 on a one-advisor-call turn) and the advisor's own pass istype: "advisor_message", billed separately. Real occupancy = the lasttype:"message"iteration'sinput + cache_read + cache_creation. Example from the phantom compaction below: top-level 772,186 vs iterations-last 386,762.Impact on one account (120 recent sessions, Fable 5 / Opus 5)
Suggested fix
Compute occupancy from the last
type: "message"entry inusage.iterationswhen present, instead of the top-level rollup — for the status line and for the auto-compact check. The correct number is already in the data.Workaround: PreCompact veto hook (verified end-to-end on 2.1.232)
A
PreCompacthook on theautomatcher re-reads the last assistant usage fromtranscript_path; ifusage.iterationsshows ≥2type:"message"passes and the real (last-pass) occupancy is below the auto-compact threshold, it exits 2 to veto that compaction — the meter recovers by itself on the next turn. Genuine compactions (single pass, or real occupancy actually over the threshold) pass through; manual/compactis untouched; the hook fails open on any error.Verified two ways:
--settings, threshold 68k). Control: auto-compacted at preTokens 80,625. With an exit-2 PreCompact hook onauto: zerocompact_boundaryentries, session continued to 109,677 tokens and completed normally.Registration (user or project settings):
Known residual: on a model whose true window is below the configured threshold the guard could defer a needed recovery compaction by one request. And per the hooks docs, blocking the error-recovery trigger surfaces the underlying API error for that request — with this guard that path stays reachable because a genuinely full context (real ≥ threshold) is always allowed through.
Still present on 2.1.255 and 2.1.258 (2026-09-03), on macOS — the thread's platform labels are
platform:windows/platform:linux, so adding a third. Corroborating @joshua-angulo's 2.1.232 binary-level confirmation and extending the observed range by ~26 releases.This is a longitudinal scan of one machine's whole local transcript archive rather than a single session, so it also gives a per-version rate, which I don't think has been posted yet.
Method
Scan every
~/.claude/projects/**/*.jsonl. For each entry carryingcompactMetadata.preTokens, take the preceding assistant message'susageand compare:input_tokens + cache_creation_input_tokens + cache_read_input_tokens(top level — what the eligibility check reads)max()of the same sum computed per entry inusage.iterations[]Deduplicated by compaction-record
uuid; the same session appears in multiple files on disk, and counting per file inflates every total roughly 2x. Classified as inflated when reported/real > 1.5. Script at the bottom.Results
176 distinct compaction records; 171
trigger: "auto"; 165 of those have a preceding usage record.87 of 165 (53%) fired off a rolled-up count.
Ratio is tight and matches the (N+1)x arithmetic already reported here: min 1.97, median 1.98, max 2.00.
Real context at the moment those compactions fired: min 238,215 · median 408,328 · max 757,862 tokens.
Rate by CLI version
| version | clean | inflated | inflated % |
|---|---:|---:|---:|
| 2.1.217 | 8 | 0 | 0% |
| 2.1.219 | 16 | 0 | 0% |
| 2.1.221 | 6 | 8 | 57% |
| 2.1.222 | 13 | 4 | 24% |
| 2.1.227 | 8 | 3 | 27% |
| 2.1.229 | 11 | 9 | 45% |
| 2.1.234 | 0 | 3 | 100% |
| 2.1.237 | 6 | 22 | 79% |
| 2.1.246 | 2 | 6 | 75% |
| 2.1.247 | 5 | 9 | 64% |
| 2.1.255 | 2 | 21 | 91% |
| 2.1.258 | 1 | 2 | 67% |
Two caveats on that table, stated so nobody over-reads it: per-version samples are small (3–28 events), and this is one machine's workload, so the mix of multi-iteration turns is not necessarily representative. What the counts do support is that the rate is not going down across ~40 releases, and the two most recent versions with double-digit samples (2.1.237 at 79%, 2.1.255 at 91%) are the highest in the set.
#84738reports the rollup format first appearing on 2.1.219. In this archive 2.1.219 is 0/16 and the first inflated event is on 2.1.221 — consistent, slightly later.Affected models
claude-opus-5(51),claude-fable-5-1(23),claude-fable-5(12),claude-sonnet-5(1). All 1M-context seats.Most recent example (2.1.258, today)
autoCompactWindowon this machine was 750000 at that point, so the effective trigger is 750000 − 20000 − 13000 = 717,000 reported tokens. A 1.98x rollup means compaction fires at roughly 358K real context — under half the configured window, on a 1M seat.Environment
latestchannel)model: "opus[1m]",autoCompactWindowexplicitly set (values varied 500000 / 700000 / 750000 / 800000 across the window scanned — the ratio is independent of it)advisorModelset; the advisor is used regularly, consistent with @johnsfleming's note that plainmessagecontinuation/retry iterations trigger it tooRepro script
Why this still matters four months on
The transcript carries the correct per-iteration figure the whole time —
usage.iterations[]is right there in the same object the eligibility check already reads. The fix ismax()instead of the implicit sum. Meanwhile, on a 1M seat with the advisor in normal use, roughly half of all auto-compactions destroy a session's context at about half the configured window, silently, and the user is told the conversation ran out of room.Still present on 2.1.268 (win32 build 2026-09-10, sha 8d19e585) — with data on why
autoCompactEnabled:falseor a PreCompact veto alone does not help on a 1M window, and a live-tested workaround that keeps auto-compaction.Numbers from one machine (Opus 5 [1m] + advisor): 18 auto-compactions across 2.1.252–2.1.268. 17 were phantom: each fired on the request right after a response with
usage.iterations=[message, advisor_message, message], andcompactMetadata.preTokenswas ≈2.0× the lastmessageiteration (e.g. 976,488 vs 481,788; 1,099,753 vs 546,869). 13 of the 18 happened in subagents / workflow agents, which inherit the advisor. The only genuine one fired at a real 925k.Where the number comes from in 2.1.268 (minified names, byte offsets in the win32 binary):
Gm(@191708841) =wK(last assistant usage) + estimate(tail);wK(@191706933) =input + cache_creation + cache_read + outputof the top-level usage.Gpe(@191707052) picks the lastmessage/fallback_messageiteration — it exists, but only the request journal and rewind use it, not compaction.kFe/spt(@191699491) for a 1M window: compact at ≥ 967,000, blocking limit at ≥ 977,000 (window − 3000), andsptchecksblockedbeforecompact.Why the obvious workarounds fail on 1M (both reproduced live, see Test)
autoCompactEnabled:false/DISABLE_AUTO_COMPACT: the main loop still runs the local pre-flight check (@193554865):``
js
`let Jd=!1,hp=Tp()&&xF()&&(…);
if(Yo.kind!=="compacted"&&T!=="compact"&&!hp&&!Jd){
if(fse(Gm(_r,…)-sl,model,autoCompactWindow).level==="blocked") … return{reason:"blocking_limit"}}
Tp()With auto-compact off,
is false, so the check runs on the same inflatedGm`: the turn ends with a synthetic "Prompt is too long" at ~488k real and stays stuck, because the synthetic message is skipped when looking up the last usage (matches #81620).auto: the same check runs right after the hook blocks, so the veto only helps while the inflated figure is in [967k, 977k), i.e. real ≈ 483.5k–488.5k. Above that it turns a phantom compaction intoblocking_limit.Workaround that keeps auto-compaction — both parts are needed
"env": { "CLAUDE_CODE_BLOCKING_LIMIT_OVERRIDE": "10000000" }—sptuses it (when > 0) instead ofwindow − 3000; on the main-loop pathfseis called with three arguments, so the env value is honored. The API stays the judge of real overflow. Undocumented, may change.autothat vetoes only phantom compactions: the last response has ≥ 2messageiterations (or aserver_tool_usewheniterationswas not persisted) and the real occupancy — lastmessageiteration + tail estimate + 30k margin — is below the threshold. Genuine compactions still run. It stays safe when the env var is missing (vetoes only inside the safe band) and allows compaction once a synthetic "Prompt is too long" has already appeared.Two things I learned from the live runs that matter for any hook here:
autocompact: routing through reactive, and an exit 2 there is logged asReactive compact blocked by PreCompact hook→Reactive compact skipped; the turn continues when the blocking level is not reached.agent_id, even when a subagent compacts (keys:custom_instructions, cwd, hook_event_name, prompt_id, session_id, transcript_path, trigger;transcript_pathis the main session). So the hook looks at the session transcripts (main +subagents/**/agent-*.jsonl) that ended with a user turn most recently, and vetoes only when all of them are phantom. Records of one response can be interleaved with results of parallel tool calls, so it groups them bymessage.id.<details><summary>Hook (tested on 2.1.268, Windows)</summary>
Registration:
</details>
Test. Headless
claude -pon 2.1.268 withCLAUDE_CODE_DISABLE_1M_CONTEXT=1(200k window: compaction threshold 167,000, blocking limit 177,000), ~120k real context from Read results, then one advisor call:| Run | Setup | Result |
|---|---|---|
| P1a | no hook, the whole context in the last user message |
level=blocked, "fixed prefix > threshold — compaction cannot help", synthetic "Prompt is too long" at ~130k real of 200k || P1 | no hook | phantom auto-compaction,
preTokens242,313 vs 121k real || P2 | hook veto, no env override | exit 2 → "Reactive compact skipped" → synthetic "Prompt is too long" (
blocking_limit) || P3 | hook + env override | exit 2, no compaction, no block; the next response reports 121,543 in one iteration and the turn completes |
| P4 | hook + env override, context grown by single-iteration turns (auto-compact window 130k) | two genuine compactions, both allowed |
| P5 | hook + env override, a subagent calls the advisor (window 150k) | no
agent_idin the input; the hook found the subagent transcript and vetoed; the subagent completed |Replay against the 18 recorded auto-compactions (every session transcript cut at the compaction start): 16 of 17 phantoms vetoed, the genuine one allowed. The one miss was a parallel subagent with a normal response that had become ready 1.5 s earlier, so the hook chose to allow. Without the env override only the 3 in-band cases were vetoed.
Fix suggestion, same as earlier in this thread: compute occupancy from the last
type:"message"entry ofusage.iterations— the API reference says "Calculate the context window size from the lastmessageentry" — inGm, in the blocking check and in the context indicator.Gpealready does exactly that.I've confirmed this is already fixed on 2.1.278 (Windows), in both Claude Desktop and the CLI.
The fix is listed in the 2.1.273 changelog:
In Claude Desktop with Sonnet 5 (1M), I called the advisor at 92% of real context.
Auto-compact did not fire, and the context meter stayed at 92%.