aieveryminute

Forcing the cache expiry everyone warns about made the session 7.6% cheaper

Guides blame a five-minute prompt cache for draining your quota. On a subscription Claude Code asks for the one-hour cache instead, and an eight-minute break really does cost 39 tokens rather than a rebuild. But the one-hour tier bills every write at 2x list against 1.25x, and across a whole session that premium cost more than the expiry it was preventing.

The advice is specific and it is everywhere: “If the cache expires (after 5 minutes of inactivity), the next request is treated as a cache miss. Claude re-processes and re-caches the full prefix, billed at the higher cache write rate.” Issue #46829 alleges the default silently dropped from an hour to five minutes in early March 2026 and claims 17.1% overpayment. It was closed as not planned.

So: build a session, walk away for eight minutes, come back and see what the next turn costs.

Turn after an eight-minute break Tokens written Cost
Default 39 $0.017
FORCE_PROMPT_CACHING_5M=1 17,613 $0.118

The expiry is real, it is expensive, and on a subscription it is not happening. Claude Code asks for the one-hour cache, so the break costs 39 tokens.

Then I summed the whole session, and the sign flipped.

Three-turn session Turn 1 Turn 2 Turn 3 Total
Default $0.199 $0.184 $0.017 $0.400
Forced five-minute $0.133 $0.118 $0.118 $0.370

Forcing the cache expiry everyone warns about made this session 7.6% cheaper, and every forced run beat every default run with no overlap.

Why the protection costs more than the thing it prevents

The one-hour cache is not the same product at a longer duration. It bills writes at 2x the base input rate where the five-minute tier bills 1.25x. You pay that premium on every single write, and it refunds you only on a turn that follows a break longer than five minutes.

That rate difference is not quoted at you, it falls out of the corpus. Applying $5 base input, $25 output, $0.50 cache read, $6.25 five-minute write and $10 one-hour write per million tokens to the measured turn leaves a residual of exactly $0.000250 in all three arms, which is exactly 10 output tokens. Arms with completely different cache splits landing on the same clean integer is what confirms the rates, and it is published as cost_model_reconciliation.

This session had three turns and one such break. The premium was charged three times and paid back once, and once was not enough.

The cleanest way to see it is that the forced arm wrote 50% more cache and still cost less:

Across all three turns Default Forced five-minute
Cache tokens written 35,139 52,731
Cache tokens read 87,370 70,311
Cost $0.400 $0.370

More work, less money. That isolates the cause to the write rate rather than the volume.

The arm that makes any of this publishable

The forced arm is not decoration. Without it the post would say “an eight-minute gap changed nothing”, and that sentence is indistinguishable from a measurement too blunt to detect a change.

Five runs per arm, no exclusions:

Arm Gap Tokens written on the measured turn Runs
Default 60s 39 kept in 5 of 5
Default 480s 39 kept in 5 of 5
Five-minute forced 480s 17,613 lost in 5 of 5

“Lost” is the corpus’s own threshold: more than 1,000 tokens written on the measured turn. Note the forced arm did not lose everything. It rewrote 17,613 tokens of the 33,709 the measured turn presented and still read the other 16,096 back, a 47.7% hit rate, so this is half a rebuild rather than a cold start.

The flag is shown working rather than asserted. On the measured turn, where the tier split reconciles exactly to the total written:

default    turn 3 wrote      39   ->  39 to the 1h tier,      0 to the 5m tier
forced_5m  turn 3 wrote  17,613   ->   0 to the 1h tier, 17,613 to the 5m tier

A separate pre-run control on a trivial prompt is published as flag_control, from two directories with equal-length names so the prefixes match exactly: an identical 5,897 tokens written in both, all to the one-hour tier by default and all to the five-minute tier when forced. That split is not in modelUsage. It sits per API call under usage.iterations[].cache_creation.

Added the same day: it does expire, at the hour

The section above measured an eight-minute break, which is what the five-minute claim is about. It left the other end open, so I bracketed it: 55 minutes against 70, three runs each, same harness and same session shape.

Idle gap TTL Tokens written on the measured turn Runs
55 minutes default 39 kept in 3 of 3
70 minutes default 17,622 lost in 3 of 3
70 minutes forced five-minute 17,616 lost in 3 of 3

The default cache survived 55 minutes and was gone by 70, which is what a one-hour TTL should do.

This matters for the eight-minute result more than it looks. Until now every expiry in this corpus came from the arm with the flag forced on, so a sceptic could ask whether the instrument can see a default-arm cache die at all. It can. Same harness, no flag, 70 minutes, and it rebuilt 17,622 tokens.

It brackets the boundary rather than locating it. Nothing here tests whether it sits exactly at 60 minutes.

The first version of this was blind, and its own control caught it

I first put the idle gap between turn 1 and turn 2 and measured turn 2. Every arm came back flat, including the forced one, so the default arms looked clean and meant nothing. That corpus was not kept and is not published; what is published is the diagnostic that explains it, one run with five-second gaps throughout:

turn1            read= 37,853  create= 17,519
turn2 (gap 5s)   read= 15,971  create= 17,548
turn3 (gap 5s)   read= 33,519  create=     31

Turn 2 writes ~17.5k at a five-second gap. It is the first resume establishing a new cache breakpoint over the grown conversation, so it cannot answer a question about idle time no matter how long you wait. Turn 3 is the turn that is nearly free when the cache is alive.

An earlier post here tried to measure cache lifetime and gave up, reporting “two gaps of the same length producing opposite results”. That is what reading the wrong turn looks like.

What this does not settle

Where the break-even is. This corpus holds exactly one session shape: three turns, a ~33,500-token prefix, one eight-minute break. No run varies the number of breaks or the size of the prefix. Where the balance flips for a different shape is not measured, and no direction is claimed for it. It is easy to sketch an argument that more breaks favour the one-hour tier, and this site has been wrong before about a bias direction it reasoned its way to instead of measuring, so that argument stays out of the post until there are runs behind it.

Eight minutes is not an hour. Closed the same day, see the bracket above: the cache kept its prefix at 55 minutes and lost it at 70.

Subscription only. An API key, Bedrock, Google Cloud’s Agent Platform, Microsoft Foundry and Claude Platform on AWS all default to five minutes per the docs, and none was tested here. Both halves of the trade would apply to them, the expensive turn after a break and the cheaper write rate, and this corpus measured neither on that path. It does not say what the net is there.

Two documented routes still give a subscription the short cache, and neither is measured: exceeding your plan limit and drawing on usage credits, and subagents, which the docs say “use the five-minute TTL even on a subscription”. Both are plausible reasons someone sees a five-minute split in their own logs. This corpus cannot say whether either explains anyone’s bill, and does not.

The arms’ prefixes differ by 0.4%. The forced arm ran from a longer directory path, and the working directory sits in the system prompt, so its established prefix was 33,670 tokens against 33,536. The default arms agree with each other to within 8 tokens. Rather than argue about which way that pushes, here is what it did: the forced arm touched more tokens than the default arm on every one of the three turns, 56,200 against 55,935, then 33,672 against 33,538, then 33,711 against 33,577. The extra tokens sit on the arm that came out cheaper. The harness now pads arm directory names to equal width, and the flag_control pair shows that working: run from equal-length directories, the two conditions wrote an identical 5,897 tokens.

This says nothing about March 2026. Measured 2026-08-13 on 2.1.229; the issue alleges a regression five months earlier.

Controls

Expiry, by forcing it rather than assuming it. The forced arm rebuilt in 5 of 5 runs at the gap the default arm sailed through.

Arrival, 15 of 15. Both the setup turn and the measured turn had to echo a planted marker as an exact string, so a “cache miss” cannot be a resume that silently carried nothing.

Same session, 15 of 15, comparing turn 1’s returned id against the one turn 3 reported.

The gap was measured, not assumed, and is published per run.

Isolation on every call, --setting-sources project, asserted onto the command line. Totals from modelUsage.

All 15 runs, the flag control and the design diagnostic are published in cache-ttl.json.

POSTaieveryminute.com#context-costbuilt 2026-08-31 17:47 UTC