#12392·oh-my-pi

Anthropic prompt cache still head-only on v18.2.5 despite ab88e931 (follow-up to #12318)

Author: renanlidoCreated Sep 18, 2026Updated Sep 18, 2026

Follow-up to #12318: still reproducing on v18.2.5 (fix ab88e931 included)

ab88e9313fad ("skip undecoratable tail messages when placing Anthropic cache breakpoints", PR #12332, merged 2026-09-17T12:44Z) is present in v18.2.5 (published 2026-09-17T23:07Z). The frozen head-only cache signature survives it on my machine.

Environment

  • omp/18.2.5, macOS 25.6.0 (Darwin 25.6.0), arm64, Apple M4 Max
  • Anthropic OAuth (Max), anthropic/claude-opus-5:high as default role, defaultThinkingLevel: high
  • providers.cacheRetention: auto, no PI_CACHE_RETENTION in env
  • 4 MCP servers configured (mongodb-atlas, tia, unity-mcp, tableplus); memory.backend: hindsight; autolearn.enabled: true; project AGENTS.md context file present
  • Binary installed 2026-09-18T00:32:33Z; the session below was launched 10s later (2026-09-18T00:32:43Z), so it is unambiguously the 18.2.5 build

Per-request trace, v18.2.5, single session, consecutive requests

From ~/.omp/agent/sessions/<project>/2026-09-18T00-32-43-641Z_*.jsonl (message.usage, provider anthropic, model claude-opus-5):

t         input   cacheRead  cacheWrite  output
00:34:18  44954     27040         0       1057
00:34:32  56054     27040         0        802
00:34:38  65575     27040         0        331
00:34:45  69855     27040         0        237
00:34:50  70349     27040         0        414
00:35:02  71747     27040         0        892
00:35:19  73686     27040         0       1177
00:35:32  75105     27040         0        955
00:35:56  85709     27040         0       1574
00:36:11  88555     27040         0        994
00:36:28 101415     27040         0       1090
00:36:54 102807     27040         0       1765
00:37:07 107968     27040         0        851
00:37:33 110777     27040         0       1599
00:37:54 116589     27040         0       1274
00:38:11 118751     27040         0        563
00:38:44 120605     27040         0       2177
00:38:54 123878     27040         0        627
00:39:04 124615     27040         0        357
00:39:08 127329     27040         0        194
00:39:21 133230     27040         0        895
00:39:34 134581     27040         0        725
00:39:47 137586     27040         0       1015

cache_read pinned at exactly 27,040 (the tools+system head anchor), cache_write 0 on every request including the first, input monotonically tracking the transcript. Session total at 26 requests: 2,707,688 uncached input tokens for 28,039 output tokens (97 input : 1 output).

Note the difference from the original report: there is no baseline write at all here. The head anchor was already warm from a previous session sharing the same prefix, so this session never wrote a breakpoint — it only ever read the frozen 27,040 and paid full price for the tail.

Same machine, versioned boundary

The break is at process launch time, not at workload change. Aggregated from session JSONL, provider anthropic only, uncached input per request:

day (UTC)        reqs   input/req   cacheWrite/req
2026-09-11       479          2           15264
2026-09-12      1171          2           22478
2026-09-13       781          2           31325
2026-09-14      1344          2           20459
2026-09-15       937          2           17496
2026-09-16 <15h  200          2          ~13000
2026-09-16 >=15h 455     51k-325k             ~0
2026-09-17       158      67k-231k            ~0
2026-09-18       116       129994               0

First broken request is 2026-09-16T15:24:35Z, in the session whose omp process started at that exact second. Processes launched before that timestamp kept the healthy signature (input == 2) for as long as they stayed alive; every process launched after it is broken, across 18.2.x up to and including 18.2.5.

Cost shape

Worst case is resuming a long session: the whole tail is re-billed every turn, so spend grows quadratically in turns. In one 54-minute window (2026-09-17T23:49Z - 2026-09-18T00:43Z, 191 requests, Anthropic only): 21,599,592 uncached input tokens for 87,780 output tokens (246:1). Weighting input 1x / cache read 0.1x / cache write 1.25x, that is ~23.1M equivalents against ~4.4M for the same traffic with a rolling anchor: 5.2x excess. Two Max accounts went to 100% of the 7-day meter.

Controlled A/B against the official endpoint: v18.2.0 vs v18.2.5

Both arms ran within the same minute, same cwd (so identical system/rules/AGENTS.md/skills/MCP head), same anthropic/claude-opus-5 at --thinking low, same OAuth credentials, same https://api.anthropic.com, same prompt (four sequential echo bash calls, --tools=bash, --auto-approve, isolated --session-dir). Only the binary differed.

arm A: omp-18.2.0                    arm B: omp-18.2.5
 #   input  cacheRead  cacheWrite     #   input  cacheRead  cacheWrite
 1       4          0       57109     1   32308          0       26992
 2       2      57109          99     2   32405      26992           0
 3       2      57208          97     3   32502      26992           0
 4       2      57305          97     4   32599      26992           0
 5       2      57402          97     5   32696      26992           0
   total uncached input: 12             total uncached input: 162510

13,542x more uncached input from the client version alone. Two details name the mechanism:

  • The baseline write differs: 18.2.0 writes 57,109 (head + messages), 18.2.5 writes 26,992 (head only — the same ~27k prefix frozen in the field trace above). 18.2.5 never anchors the message tail on the first request either, so this is not "the anchor stopped advancing", it is "the anchor was never placed on messages at all".
  • cacheRead advances in 18.2.0 (57,109 -> 57,402, +97/+99 per turn, matching each new tool-result turn) and is pinned in 18.2.5, with input rising by the same ~97 per turn instead.

This rules out provider-side and accounting-side explanations: identical endpoint, identical account, identical payload shape, opposite behaviour.

Reproduction

Two binaries, one command each, in any project directory with a non-trivial context head:

bash
omp-18.2.0 -p --auto-approve --no-title --tools=bash \
  --model anthropic/claude-opus-5 --thinking low --session-dir=/tmp/ab/v0 \
  "Run exactly four bash commands, one per turn, in separate sequential calls: echo T1, then echo T2, then echo T3, then echo T4. Do not read or modify any file. Answer only DONE."

omp-18.2.5 -p --auto-approve --no-title --tools=bash \
  --model anthropic/claude-opus-5 --thinking low --session-dir=/tmp/ab/v5 \
  "<same prompt>"

Then read message.usage from each session JSONL. Healthy is input == 2 with cacheWrite > 0 per request and a climbing cacheRead; broken is input tracking the transcript with cacheWrite == 0 and cacheRead pinned.

After rolling the installed binary back to v18.2.0, the same check on the installed path gives 10 uncached input tokens across 4 requests:

 #   input  cacheRead  cacheWrite
 1       4      26988       30072
 2       2      57060          99
 3       2      57159          97
 4       2      57256          97

Request 1 is informative: it reads the 26,988 head left warm by v18.2.5 and then writes 30,072 on top of it, i.e. v18.2.0 anchors the messages that v18.2.5 never anchored.

One operational note for anyone bisecting this: the effective version is bound at process exec. Long-lived omp processes started before the upgrade keep the healthy behaviour for new sessions, and starting a new session inside an already-running broken process does not help. That is why the boundary in the table above lands on a process start timestamp rather than on a release timestamp.

Remaining leads

Consistent with lead 1 from the original issue (countHeadBreakpoints exhausting the budget before the tail): this configuration has an unusually large stable head (SYSTEM + RULES + project AGENTS.md + hindsight mental-model block + 4 MCP servers' tool inventory). If the head consumes all 4 breakpoints, applyPromptCaching returns at messageBudget <= 0 and the tail is never anchored, which reproduces exactly the trace above — including the absence of a baseline write, since the head anchors were already warm.

Happy to run an instrumented build; the machine reproduces this on every session.