A large steel stockpot tipping a stream of dark reduced stock into a kitchen sink drain, a single burner knob on the range beside it turned slightly and catching a warm highlight, early morning professional kitchen, photoreal

OpenAI Gave Cache Misses Nine Reason Codes. Its Own Coding Agent Still Trips One of Them.

On 8 September OpenAI made Prompt Cache Diagnostics generally available in the Responses API. Pass an earlier response ID and the API tells you whether your prompt cache was reused, and if not, why: nine reason codes, from model_changed to context_compacted, with an estimate of how many tokens the miss affected.

One of those codes, reasoning_effort_changed, already has a fix. GPT-6 Astra accepts a configuration_update item that changes effort without touching the cached prefix. A GitHub issue opened on 5 September shows the Codex CLI still changes effort the old way, at the request level, which throws the cache away.

Eight of the nine reasons are about request settings, and those settings belong to your harness. On Astra a cached read costs $1 per million tokens and a cache write costs $12.50, so a miss is no longer just a lost discount. Sample the diagnostics, put a price on each reason, and move anything that changes per turn out of the request-level fields.

Prompt caching is why long agent sessions are affordable. Every turn re-sends the whole conversation, and a cache hit lets the model skip the part it already processed, at a tenth of the input rate. When it stops working, nothing errors. The bill just goes up.

Until last week you saw a miss only indirectly, by watching usage.input_tokens_details.cached_tokens drop and guessing why. The diagnostics replace the guess with a label.

What the API returns

Diagnostics are opt-in per request. You add comparison_response_id inside prompt_cache_options, pointing at a recent completed response you expected to share a prefix with. The response then carries a prompt_cache_diagnostics object with one of four types: cache_hit, cache_miss, comparison_response_not_found, or unavailable. It works on GPT-5.6 and later models.

A cache_miss comes with cache_missed_tokens, the estimated number of input tokens affected after the first point where the two requests diverged, an optional comparison_reusable_tokens for the size of the prefix you could have reused, and a reason. The reason is one of these:

Only input_changed is about the content of the conversation, and context_compacted is a deliberate trade. The other seven are request settings: model, cache key, tool list, output format, effort, verbosity, service tier. A user typing into a chat box controls none of them. Your harness or gateway sets all of them, on every turn.

The reason that already has a fix

Before Astra, reasoning effort was a request-level field, and request-level fields sit inside the cached prefix. OpenAI's prompt caching guide lists reasoning.effort among the settings that invalidate it. Raise effort for one hard planning turn, drop it for the next ten routine edits, and each switch looks like a different prompt to the cache.

On 3 September, alongside GPT-6 Astra, OpenAI shipped a way around that. Instead of changing the request field, you put a configuration_update item into the conversation history, directly before the user turn it should apply to, and leave the request-level effort alone. Everything before the update stays byte-identical, so the prefix stays cached. The rules are narrow: Astra only, standard single-agent mode, rejected in pro mode, no two updates next to each other, and no mixing with automatic compaction or automatic truncation.

Two days later, someone filed openai/codex issue #42996. The Codex CLI lets you change reasoning effort mid-thread from the TUI. The issue traces that setting from thread/settings/update through the session code into core/src/client.rs, where it becomes the request-level reasoning.effort on the next call. No configuration_update item appears in the model history. The mechanism OpenAI built to preserve the cache exists in the codebase and is not wired to the setting users actually touch. When I checked on 14 September the issue was still open.

Codex is not unusual here. NousResearch's hermes-agent has the same issue open as #103019, with a draft PR. And the cost was visible in Codex before Astra shipped. Issue #35416, filed in July, logged a session where switching effort from low to medium dropped the cache hit rate on that turn from 93.4% to 66.1%. Returning to an effort level already used earlier in the session did not miss, which fits the cache holding separate entries per configuration. A second report, #32533, shows the worse version: changing effort on a session of roughly 172,000 tokens broke incremental WebSocket reuse, the client tried to resend the full history, and it hit "Request blocked." about a dozen times over four and a half minutes until compaction brought the session down to around 36,000 tokens.

If the team that ships the API and the team that ships its flagship harness are a week apart, your stack is probably further behind.

What a miss costs on the new price sheet

GPT-5.6 and later changed the economics. Cache reads are billed at 0.1x the uncached input rate, and cache writes at 1.25x, where older models charged nothing extra for writes. For GPT-6 Astra that is $10 per million input tokens, $1 cached, $12.50 to write.

Take a conversation carrying a 150,000 token prefix. A hit on that prefix costs $0.15. If a changed setting forces the same prefix to be written again, that turn costs about $1.88 for the same tokens, 12.5 times as much.

Say an agent flips effort eight times across a 40-turn session at that size. The eight misses add roughly $13.80. If all 40 turns had hit, the cached reads for the whole session would have cost $6. The cache lifetime on GPT-5.6 and later is also fixed at "30m", so a session that pauses an hour for review starts cold regardless.

Where the other reason codes come from

tools_changed. Names, descriptions, schemas and ordering are all in the prefix. An MCP server that reconnects and lists its tools in a new order, or a harness that appends tools mid-session, changes it. OpenAI's guide says to keep the list stable and use allowed_tools to restrict what the model can call on a given turn.

model_changed. Routers that send the cheap turns to gpt-5.6-terra and the hard ones to Astra save on token price and pay again on cache writes. Each model keeps its own cache.

service_tier_changed. Promoting a latency-sensitive turn to a faster tier and dropping back afterwards splits the cache the same way.

text_format_changed and verbosity_changed. One structured-output turn in the middle of a free-text conversation, say to extract a JSON plan, changes the prefix for that turn.

prompt_cache_key_changed. Keys should identify a user or a session. A key built from a request ID guarantees a miss every time.

context_compacted. Compaction replaces earlier history with a shorter version, so the first request afterwards reuses less. That is the intended trade. Count it, but do not fight it.

input_changed. The one that looks like content is often plumbing: a timestamp, a request ID, or a retrieved snippet rendered near the top of the system prompt, so the prefix changes on every call.

Wire it in this week

Sample the diagnostics. On a fraction of turns, say one in twenty, pass the previous turn's response ID as comparison_response_id and log the type, reason and cache_missed_tokens. Group by reason across a day of traffic. The diagnostics are best-effort and the token counts are estimates, so keep billing numbers on cached_tokens and cache_write_tokens in the usage block.

Put a price on each reason. Multiply missed tokens by the gap between your model's write rate and read rate. A ranked list of reasons with a dollar figure beside each tells you which fix to do first.

Audit every per-turn request field. Search your harness for code that sets reasoning.effort, text.verbosity, text.format, tools, service_tier or model per call. Each one either stays fixed for the session, moves into history as a configuration_update, or moves into allowed_tools.

Pick effort once if your harness is behind. Until your framework uses configuration_update, choose reasoning effort at session start and leave it. If you do switch, go back to a level the session used in the last half hour rather than a new one.

Push volatile content to the end. Stable instructions and tool definitions first, anything that changes per request last. On GPT-5.6 and later you can mark explicit breakpoints on the stable boundaries, up to four cache writes per request.

OpenAI built the fix for effort changes and the label for the miss in the same week, and its own CLI still sends the old request. Turn the diagnostics on before you assume your harness is doing better.