[BUG] Advisor is omitted from internal fork requests, so /compact re-bills the whole conversation as a cache write

Status Open
Reported on v2.1.239
Maintainer reply None cached
Activity 6 comments · opened Aug 22, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code

What's Wrong?

/compact is served by an internal forked request: Claude Code re-sends the conversation to
the model with a summarisation instruction appended, using query_source: "compact". That
fork is supposed to share the main loop's prompt-cache prefix.

When the advisor tool is enabled, it does not. The fork is sent with a different tools
array and a different system prompt than the main loop, so it cannot reuse the conversation
cache the main loop wrote seconds earlier. Every compaction re-processes the whole
conversation body as a cache write.

Setting CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1 makes the two request bodies identical apart
from cache_control, and the summarisation request then reads the entire conversation from
cache with zero cache creation.

This is deterministic. It fires on every compaction, for every user who has the advisor
enabled, and the cost scales with context size.

A/B result

Same input file, same model, same procedure. The only difference is
CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1. Measured with OpenTelemetry
claude_code.api_request events.

| | advisor enabled | advisor disabled |
|---|---|---|
| context before /compact | 124,770 | 123,698 |
| last main-loop request | read 119,636 / create 5,134 | read 118,464 / create 5,234 |
| compact cache_read_tokens | 64,813 | 118,570 |
| compact cache_creation_tokens | 54,008 | 0 |
| compact input_tokens | 6,462 | 6,561 |
| compact cost_usd_micros | 86,493 | 24,688 |

3.5x cost for one compaction on Haiku 4.5. The 54,008 tokens re-written in the enabled arm
are the conversation body, which the main-loop request had cached seconds earlier.

The 64,813 that still reads in the enabled arm is a static head left in the cache by an
earlier request of the same fork shape, from a previous test session. It is not a partial hit
against the main loop's entry. With no such entry present the summarisation request reads
nothing at all:

| model and condition | context | compact read | compact create |
|---|---|---|---|
| haiku-4.5, real session via --fork-session | 78,411 | 0 | 75,249 |
| sonnet-5, MAX_THINKING_TOKENS=0 | 91,327 | 0 | 89,121 |
| sonnet-5, ENABLE_PROMPT_CACHING_1H=1 | 91,818 | 0 | 89,612 |
| sonnet-5, defaults | 91,634 | 31,175 | 58,259 |

Request body diff

Last main-loop request against the summarisation request, same session, seconds apart,
captured off the wire with a local recording proxy in front of api.anthropic.com.
Reproduced on three separate sessions.

| | main loop | compact |
|---|---|---|
| tools | 67 entries | 66 entries |
| missing entry | | advisor, which is index 66, the last element |
| system[2] length | 10,918 chars | 8,899 chars |
| relation | main equals compact plus a trailing 2,019-char # Advisor Tool section | |
| cache_control on system[1], system[2] | {"type":"ephemeral","ttl":"1h"} | {"type":"ephemeral"} |
| messages | 23 | 23 |
| model, thinking, effort, max_tokens | identical | identical |

The summarisation instruction is appended as a text block to the final user message, so the
message count does not change.

With CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1 the same comparison gives 66 tools on both sides
and byte-identical system blocks, and the summarisation request achieves a full cache read.

I am reporting the controlled A/B rather than asserting an implementation detail. The
one-tool and 2,019-char system difference coincides exactly with the miss, and removing the
difference removes the miss.

What I ruled out

  • Cache TTL. The two requests do ask for different TTLs, but that is not the cause. Forcing

ENABLE_PROMPT_CACHING_1H=1 so both ask for 1h did not restore the hit (row 3 above: read
0, create 89,612, confirmed as a 1h write by reconciling cost_usd_micros against the 2x
multiplier). Conversely the advisor-disabled arm still has the TTL difference and still
achieves a full read. A 5-minute breakpoint reads a prefix written with ttl: "1h" fine.

  • Effort. The api_request events report effort: high for both requests.
  • Thinking configuration. With MAX_THINKING_TOKENS=0 both requests send

{"type":"disabled"}, verified in the captured bodies, and the miss persists.

  • Message content. Same message count, and the only addition is a text block on the final

user message.

Scope beyond /compact

The same mismatch appears in the other internal forked requests I captured, including
prompt_suggestion and generate_session_title. In one session prompt_suggestion
re-processed 90,264 tokens as a cache write on a single turn. The advisor appears to be
attached only for a small set of query sources, with internal forks excluded, but this report
stays focused on /compact because that is where the cost is largest and most predictable.

Not a duplicate of #81967

#81967 concerns the tools array changing over time within one conversation, primarily as
LSP tools are added or removed, and deferred tool schema loads growing tools[].

This report is different. The main-loop request shape is stable and the fork request shape is
stable. They are permanently different from each other whenever the advisor is enabled. No
mid-session mutation is required, and every warm-cache compaction reproduces the miss.

Related but distinct:

  • #77306 is a session-level fork losing the parent cache, triggered by session-specific

system prompt content. This report concerns internal forked requests such as compaction.

  • #81620, #84738 and #81029 concern advisor usage accounting inflating the auto-compact

counter. Different mechanism, different place where the loss occurs. Fixing those would not
fix this.

What Should Happen?

An internal forked request should carry the same tools array and system prompt as the main
loop, so that a warm-cache /compact reads the conversation from cache instead of re-writing
it.

If the advisor must not be callable from inside a fork, the fix should keep the tool declared
and reject the invocation, for example by marking it disabled in the fork and returning an
error if it is called. Removing the declaration from the request is what breaks the cache.

This is also what the documentation currently promises.

docs/en/prompt-caching, section "Compacting the conversation":

To produce the summary, Claude Code sends a separate request with the same system prompt, tools, and history as your conversation, plus a summarization instruction appended as a final user message. While the cache is warm, that request reads your prefix from the cache, so a mid-session /compact costs a fraction of what the context size suggests.

With the advisor enabled the system prompt and tools are not the same, and the request does
not read the prefix from the cache.

docs/en/advisor, section "Impact on prompt caching":

Enabling or disabling the advisor mid-session does not invalidate your main model's prompt cache.

That claim is about the main loop and may well hold there. It does not extend to internal
forks, and nothing in the docs warns that enabling the advisor turns every compaction into a
full cache write.

Error Messages/Logs

# advisor enabled: claude_code.api_request events, Haiku 4.5, MAX_THINKING_TOKENS=0
 107 repl_main_thread  in     5  read 114091  create   5545  out  109  cost   23049
 114 repl_main_thread  in     5  read 119636  create   5134  out   13  cost   22302
 119 compact           in  6462  read  64813  create  54008  out 1208  cost   86493

# same rig plus CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1
 101 repl_main_thread  in     5  read 113016  create   5448  out  110  cost   22753
 108 repl_main_thread  in     5  read 118464  create   5234  out    4  cost   22339
 113 compact           in  6561  read 118570  create      0  out 1254  cost   24688

# request body comparison, advisor enabled
main tools 67   comp tools 66   only in main: ['advisor']  (index 66 of 67)
main sys  [70, 57, 10918]       comp sys [70, 57, 8899]
main sys[2] == comp sys[2] + 2019 trailing chars ("# Advisor Tool" section)

# request body comparison, CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1
main tools 66   comp tools 66   tools identical: True
main sys  [70, 57, 8899]        comp sys [70, 57, 8899]   system identical: True

Steps to Reproduce

Before starting, turn prompt suggestions off. prompt_suggestion is served by the same
internal fork and writes the same prefix shape, so with it enabled the compact request can
read an entry the suggestion request wrote moments earlier, and the measurement understates
the miss. I have seen exactly that: a suggestion request created 40,298 tokens and the
compact request three requests later read 40,298 with zero creation. The A/B above was taken
with suggestions disabled.

  1. Run a recording reverse proxy on 127.0.0.1:8788 that forwards to api.anthropic.com

and writes each /v1/messages request body to a file.

  1. Run an OTLP receiver on 127.0.0.1:4318.
  2. Make sure an advisor is configured (/advisor), then start a session through both:

``bash
env ANTHROPIC_BASE_URL=http://127.0.0.1:8788 \
MAX_THINKING_TOKENS=0 \
CLAUDE_CODE_ENABLE_TELEMETRY=1 \
OTEL_METRICS_EXPORTER=none \
OTEL_LOGS_EXPORTER=otlp \
OTEL_EXPORTER_OTLP_PROTOCOL=http/json \
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318 \
claude --model haiku
``

MAX_THINKING_TOKENS=0 is only there to remove thinking configuration as a variable; the
bug reproduces without it.

  1. Build roughly 120k of context. I used a generated 120 KB text file and asked the model to

read it in full with repeated Read calls.

  1. Run /compact.
  2. Look at the claude_code.api_request event with query_source: "compact". It reports

cache_creation_tokens covering the whole conversation and little or no
cache_read_tokens.

  1. Compare the last repl_main_thread request body with the compact request body. The

tools array differs by one entry and the last system block differs by the trailing
advisor section.

  1. Repeat the whole run with CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1 added. The two bodies now

match and the compact request reads the conversation from cache with zero creation.

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

Not known. Only 2.1.239 was measured.

Claude Code Version

2.1.239 (Claude Code)

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

WSL (Windows Subsystem for Linux)

Additional Information

  • Account is Claude Max, first-party API, no gateway or proxy in normal use. The recording

proxy was added only for this investigation.

  • Reproduced on claude-haiku-4-5-20251001 and on claude-sonnet-5.
  • Also reproduced on a real working session rather than a synthetic one, by duplicating it

with claude -r <id> --fork-session and compacting the copy: 78,411 tokens of context,
compact read 0, create 75,249.

  • The summarisation request is not written to the transcript, so this cannot be diagnosed

from ~/.claude/projects/*/*.jsonl. The transcript records the main loop's cache write and
the compact_boundary entry, with nothing in between. OpenTelemetry or a proxy is needed
to see it.

  • Practical impact for a heavy user: on a large Opus session, every compaction pays a full

cache write over the whole context instead of a cache read, at 1.25x base input rather than
0.1x. That is roughly a 12x difference on the dominant term, and it repeats for every
compaction in a long session.

View original on GitHub ↗

5 Comments

motonari728 · 9 days ago

The platform:wsl label is a misfire — this is not WSL-specific.

The cause is request construction: the internal forked request omits one tool entry and a
trailing system prompt section relative to the main loop. That is independent of operating
system, terminal and shell. WSL is simply where I happened to measure it.

I would expect it to reproduce on macOS and native Linux for any user who has an advisor
configured. area:cost and area:core are right; platform:wsl will send triage in the
wrong direction.

motonari728 · 9 days ago

Additional cost data, since this is not limited to /compact.

prompt_suggestion is served by the same internal fork, fires at the end of essentially
every turn once the conversation has two assistant messages, and writes with ttl: "1h", so
its writes are billed at 2x base input — the same multiplier as the main loop.

With the advisor enabled it cannot read the main loop's entry either, so it builds and then
maintains a second cache lineage alongside the main one. The increments it writes over a
session sum to roughly the same total as the main loop's own writes. Not a parallel request,
a parallel lineage.

Four identical short turns on Haiku 4.5, only CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1 differing:

| | advisor enabled | advisor disabled |
|---|---|---|
| main loop cache_creation | 30,879 | 29,909 |
| prompt_suggestion cache_creation | 29,973 | 12 |
| main loop cost_usd_micros | 71,365 | 68,846 |
| prompt_suggestion cost_usd_micros | 67,182 | 10,213 |

Per suggestion call:

advisor enabled:  read      0 create 29,819 | read 29,819 create 77 | read 29,896 create 77
advisor disabled: read 29,783 create      4 | read 29,850 create  4 | read 29,917 create  4

With the advisor disabled the suggestion request reads the main loop's entry and writes four
tokens. With it enabled it duplicates the lineage. Total session spend here was 1.75x, on a
trivial 30k conversation.

The 2x multiplier is confirmed from cost rather than assumed:
29,819 x $2/M + 343 x $1/M + 22 x $5/M = 60,091 micro-USD, matching the reported
cost_usd_micros exactly. A 5m write at 1.25x would give 37,727.

There is also a guard that looks like it was meant to prevent this spending. Suggestion
generation is skipped when input_tokens + cache_creation_input_tokens + output_tokens
exceeds 10,000 on the last assistant message. But it inspects the main loop's message, not
the fork's. With the advisor enabled the main loop stays warm and passes the guard, while the
fork behind it pays a full write.

Recommendation for anyone reading this

If you have an advisor configured, turn prompt suggestions off until this is fixed. With both
enabled you pay a second full set of cache writes, at the same 2x multiplier, for the whole
session.

This is easy to misread. I noticed my usage climb after enabling the advisor and put it down
to the advisor calls themselves being expensive. They are, but a large part of what I was
seeing was this: a duplicated cache lineage maintained turn after turn by the suggestion
request. The advisor's own calls are visible in the transcript; this is not, which is
probably part of why it has gone unreported.

Disabling suggestions keeps the advisor and removes most of the extra spend. What remains is
the compaction request itself, which is what this issue is about.

NetBr3ak · 8 days ago

Your A/B with cost_usd_micros, and pulling prompt_suggestion out as its own
cache line, is the only writeup of this I've seen with numbers in it rather
than impressions.

The piece missing is the price. A cache write costs 1.25 times a normal input
token, so re-billing a conversation as a write is 25 percent more expensive
than sending those same tokens as plain input, and there's no read afterwards
to earn it back. Every compaction pays that on the whole prefix.

On ttl "1h" it's worse. Writes cost 2x there, and break-even moves from 0.28
reads per write to 1.11.

You filed the same shape against OpenCode earlier, where an injected
prunable-tools list broke the prefix. Same pattern, two harnesses.

How are you accounting for this per session rather than per request?

yingxiangge · 8 days ago

This makes sense mechanically — compact rebuilds the conversation prefix, so the entire compacted context
becomes a fresh cache_creation event. The question is how big that payload actually is.

From analyzing 82 of my own sessions: the average prompt at compaction time was around 292K tokens, and 93.9%
of that was tool output (grep results, file reads, build logs). So a single /compact can easily trigger a 250K+
cache_write in one shot.

I wrote a read-only CLI that plots the per-turn prompt size over a session — you can visually see the sawtooth
pattern where context grows linearly, /compact drops it, then the cache_write spike hits:

https://github.com/yingxiangge/agent-cost

Might help quantify exactly how much each /compact is costing in your case.

motonari728 · 23 hours ago

Still not fixed as of 2.1.251. Reproduced under the same conditions as the original report (Haiku 4.5, MAX_THINKING_TOKENS=0): the compact request still omits advisor from the tools list (76 vs. 77) and drops its 2,019-char section from system[2] — the same numbers as on 2.1.239. The A/B is also unchanged: one compaction costs 3.0x with the advisor enabled (cache read 0 / create 40,634) vs. disabled with CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1 (read 40,631 / create 0).

Showing cached comments. Read the full discussion on GitHub ↗