You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
GPT-5.6 supports an explicit prompt_cache_breakpoint on the content block that ends a reusable prefix. Codex's own vendored migration guide (codex-rs/skills/src/assets/samples/openai-docs/references/upgrading-to-gpt-5p6-sol.md:210) describes this exact failure mode:
GPT-5.6 implicit caching places a managed breakpoint near the latest user or tool message and no longer relies on 128-token rounding. A prompt with a large stable prefix followed by a changing suffix can therefore lose cache hits even when the stable prefix itself has not changed.
Its remedy, at line 242: "Place explicit breakpoints at the actual stable rendered boundary using prompt_cache_breakpoint." And at line 244, why this now costs money: "Cache writes cost more than ordinary uncached input, so a lower hit rate can be both slower and more expensive."
Codex cannot emit the field. ContentItem::InputText and InputImage have no prompt_cache_breakpoint member or extension map, so the documented field is unrepresentable in both HTTP and WebSocket request construction.
In a controlled replay of real Codex request bodies with the same ~9k-token developer prefix, a fixed prompt_cache_key, and only the final user message changed, unmodified requests produced 0% cache hits and rewrote ~9,060 tokens each. Adding only prompt_cache_breakpoint: {"mode":"explicit"} to the final stable input_text block produced 98.6% hits and reduced warm-request writes to 123–128 tokens.
This does not affect every turn. Append-only continuation turns within a single session already reached ~98% hits. The demonstrated failure is reuse of a stable startup prefix when independent requests diverge at the first volatile user turn.
The source-level defect is the missing content-block field. Session-scoped cache keys are a separate compounding issue on the measured backend, automatic breakpoint placement is a policy decision, and prompt_cache_options is optional API completeness.
Environment
Codex CLI
0.145.0 and 0.146.0-alpha.10, tested as of 2026-07-24
Source refs
rust-v0.145.0 (25af12f7e615), rust-v0.146.0-alpha.10 (ae83c6df6ffd), and main @ 4c43465133428898aa84f0bfc02c306ed65fb66a
Model measured
gpt-5.6-sol
Also affected by the wire-type gap
gpt-5.6-terra, gpt-5.6-luna
Backend measured
AWS Bedrock Mantle, OpenAI-compatible Responses API (/openai/v1/responses)
Wire API
responses, streaming
Platform
macOS 27.0 arm64
The measurements below are from Bedrock Mantle. The serialization defect is backend-independent and directly verifiable from the Codex source, but absolute cache behavior and billing should also be reproduced against api.openai.com before being treated as universal.
Expected behavior
OpenAI's prompt-caching guide documents that GPT-5.6 and later models can mark the exact end of a reusable prompt prefix by adding:
"prompt_cache_breakpoint": {"mode": "explicit"}
to a supported content block. Content after that block may then change without invalidating the earlier cached prefix.
Codex should at minimum be able to represent and serialize this field on supported content blocks. Whether Codex places breakpoints automatically, exposes them through configuration, or both is a separate default-policy decision. The vendored migration guide explicitly advises using explicit breakpoints only for measured stable boundaries and not converting every prompt globally.
Actual behavior
Codex always relies on the implicit managed breakpoint because it has no wire representation for an explicit one. When requests share a large startup prefix but differ at the first volatile user turn, Codex cannot mark the actual stable boundary.
On the measured workload, those requests missed and rewrote essentially the entire eligible prefix. In contrast, ordinary append-only turns within one session cached normally because each prior turn became stable prefix for the next.
Minimal source proof
The content-block field is unrepresentable
codex-rs/protocol/src/models.rs:702 in rust-v0.145.0 and :706 on the cited main revision:
There is no prompt_cache_breakpoint field and no #[serde(flatten)] or extra-fields map. The API supports breakpoints on input_text, input_image, and input_file, but Codex cannot serialize one on any of those blocks.
The top-level cache options are also absent
ResponsesApiRequest and ResponseCreateWsRequest both contain prompt_cache_key, but neither contains prompt_cache_options or an arbitrary request-body extension map:
This is not required for the measured hit-rate recovery: the A/B below used a content-block breakpoint without sending prompt_cache_options. It is listed separately because the same GPT-5.6 API surface is incomplete in both the HTTP and WebSocket request structs.
Configuration cannot work around the missing nested field
ModelProviderInfo accepts connection-level customization such as base_url, wire_api, headers, query parameters, and retry controls, but no request-body transformation. The schema uses additionalProperties: false, so a user cannot add a nested prompt_cache_breakpoint through config.toml.
A top-level request-body passthrough such as #34569 could expose prompt_cache_options, but it would not solve the core bug unless it also supported structured transformation inside input.
Controlled reproduction
Procedure
Configure Codex with a recording custom model_provider using wire_api = "responses".
Capture a real codex exec request body.
Replay the body several times with:
one fixed prompt_cache_key;
an identical developer prefix;
only the final user message changed between requests.
Record usage.input_tokens_details.cached_tokens and cache_write_tokens.
Repeat with the same bodies, adding only the following field to the last input_text block in the stable developer prefix:
"prompt_cache_breakpoint": {"mode": "explicit"}
The measured bodies used the non-lite request shape because the production gateway rejects the proprietary additional_tools item. The serialization gap is the same in the Responses Lite path; only the correct placement differs.
Result
The first request in each arm was a cold write. The table below compares the three subsequent requests.
No prompt_cache_options were sent. The only request-body change was the content-block breakpoint.
Scope and non-claims
This report does not claim every Codex request misses cache. Five sequential turns in one unmodified session reached approximately 98% hits after the cold turn.
The controlled A/B establishes causality for a stable prefix followed by a changing tail. It does not establish that every cache write on the provider invoice was caused by this defect.
The source-level bug is inability to represent the field. Automatic insertion, stable-key policy, and top-level cache options may be fixed together or handled separately.
The measured requirement for a stable cross-session key is specific evidence from Bedrock Mantle. OpenAI documents prompt_cache_key as a routing and matching hint, not a guaranteed hard cache partition.
Suggested minimum fix
Add an optional breakpoint field to supported content variants, at least ContentItem::InputText and InputImage, with skip_serializing_if = "Option::is_none". Add InputFile support when that content variant exists.
Add serialization tests proving that the field is emitted only on supported content blocks and omitted by default.
Gate use through an explicit model/provider capability such as supports_prompt_cache_breakpoints. Do not infer support solely from a model-name string, because custom providers may expose different request capabilities.
Provide a code path that can mark the end of a measured stable rendered prefix. Automatic placement can remain conservative or opt-in; the minimum requirement is that the documented field be representable.
Follow-up improvements that are useful but not required to establish or fix the core serialization defect:
derive or expose a cache key that can remain stable across independent requests with the same startup context, while respecting the documented request-rate guidance per key;
add prompt_cache_options: Option<PromptCacheOptions> to both HTTP and WebSocket request structs;
audit CompactionInput, which also carries prompt_cache_key without prompt_cache_options, after confirming whether the specialized compact endpoint accepts the field.
Five sequential turns from one unmodified Codex session:
turn
input
cached
writes
hit rate
1
9,093
0
9,091
0%
2
9,244
9,091
151
98%
3
9,385
9,242
141
98%
4
9,559
9,383
174
98%
5
9,771
9,557
212
98%
This is why the issue is easy to miss when inspecting a long interactive session: the implicit tail breakpoint works for monotonically growing history.
Cross-session key interaction on Bedrock Mantle
Three captured codex exec bodies contained 5,621 / 5,641 / 5,617 input tokens and differed in their volatile tails. Each arm replayed those same fixed bodies.
configuration
per-request hit rate
total cache-write tokens
Unmodified Codex
0%, 0%, 0%
16,873
Explicit breakpoint only
0%, 0%, 0%
16,873
Stable prompt_cache_key only
0%, 0%, 0%
16,873
Breakpoint and stable key
0%, 96%, 97%
5,973 (−65%)
The inputs total 16,879 while the full-miss writes total 16,873 because the provider reported two fewer write tokens than total input on each request.
This establishes that, on the measured backend, a stable key was also needed for independent sessions to realize reuse. It is a compounding key-policy issue, not evidence that the missing breakpoint field itself is backend-specific.
Other causes tested
A 101,448-token prefix cached at 99% with a breakpoint, so this was not a prefix-size ceiling.
Reasoning efforts from low through xhigh showed the same baseline miss pattern.
Carrying reasoning items and requesting reasoning.encrypted_content had no measurable effect after the arms were pre-warmed identically.
Request shapes and breakpoint placement
All three shipped GPT-5.6 catalog entries have use_responses_lite: true, but custom providers may use the non-lite path.
Responses Lite
Codex prepends an additional_tools developer item and a developer message containing base instructions to input; top-level instructions and tools are omitted.
additional_tools has no content block on which to place a breakpoint. A placement algorithm would need to mark the last supported content block in the measured stable developer portion, not the additional_tools item itself.
Non-lite Responses
instructions and tools remain top-level, while leading developer messages are in input:
The controlled A/B marked the last input_text block in the stable developer item. An implementation must not assume every developer-role item is stable: current time, workspace data, permissions, tool availability, and project instructions can vary. The boundary should be based on the rendered stable portion, splitting content blocks where necessary.
Cost impact and provider billing evidence
GPT-5.6 cache writes are billed at 1.25× uncached input, while cache reads are billed at 0.10×. A write therefore costs 12.5× a read. Earlier model families did not expose the same cache-write line-item cost.
AWS Cost Explorer for OpenAI GPT-5.6 Sol (Amazon Bedrock Edition), 2026-07-01 through 2026-07-23 complete days, filtered to RECORD_TYPE = Usage:
usage type
tokens
cost
share of model spend
cache_write_tokens_30m_standard
258.8M
$1,780.33
90.0%
output_tokens_standard
3.9M
$127.56
6.5%
input_tokens_standard
9.8M
$53.98
2.7%
cache_read_tokens_standard
28.6M
$15.71
0.8%
Total
$1,977.58
Cache-write tokens were 9.1× cache-read tokens. Counting uncached input, cache writes, and cache reads together gives a 9.6% aggregate cached-token share.
For comparison, the same client and gateway against OpenAI GPT-5.5 (Amazon Bedrock Edition) billed 9.17M input tokens and zero cache-write tokens over the same period.
What this evidence establishes:
cache-write volume is a material real-world cost on this GPT-5.6 workload;
the provider's implied unit prices reproduce the documented write/read multipliers;
the controlled replay shows that a correctly placed breakpoint can eliminate most warm writes for the tested request pattern.
What it does not establish:
that this defect caused every one of the 258.8M cache-write tokens;
the share attributable to cold starts, genuinely distinct prefixes, compaction, or other prefix churn;
universal savings on other providers or workloads.
Applying the controlled reductions to all invoice writes would imply approximately $1,157 at the 65% cross-session reduction or $1,745 at the 98% same-prefix A/B reduction. Those are illustrative upper bounds, not attributable savings.
Confirms that ModelProviderInfo has no generic top-level request-body extension. It could expose prompt_cache_options, but not a nested content-block breakpoint without structured input transformation.
Cache-write usage is now carried through Codex for API-key/provider responses. The issue remains open because subscription-backed responses may still report zero.
Closest prior art for preserving cache lineage in forked subagents; discussion mentions an inherited key and explicit inherited-prefix boundary, but not the missing serialization field.
Reports intermittent misses on repeated bodies. A truly byte-identical body also has an identical implicit breakpoint, so the missing explicit field does not by itself explain that issue.
Image-triggered invalidation may be adjacent because GPT-5.6 supports image-block breakpoints, but no causal link is established here.
No issue found in the searched set reports that prompt_cache_breakpoint is structurally unserializable in Codex, and no open PR found in that search addresses it.
Verification notes
The cache-relevant source was checked at rust-v0.145.0, rust-v0.146.0-alpha.9, rust-v0.146.0-alpha.10, and the cited main revision. Relevant line numbers move, but the missing fields remain.
git grep -l -E 'prompt_cache_options|prompt_cache_breakpoint' <ref> returned only the vendored migration-guide markdown asset and no Rust implementation at the checked refs.
ResponsesApiRequest changed between stable and prerelease for unrelated tool serialization, confirming the request struct has been edited recently without adding these cache fields.
The replay fixtures, proxy transform, and raw provider responses are not embedded in this issue body. The source-level defect is independently verifiable without them; the controlled measurements should be treated as author-supplied evidence unless a sanitized bundle is attached.
The AWS figures can be reproduced with Cost Explorer by filtering the model service to RECORD_TYPE = Usage and grouping by USAGE_TYPE. UsageQuantity is reported in millions of tokens.
Summary
GPT-5.6 supports an explicit
prompt_cache_breakpointon the content block that ends a reusable prefix. Codex's own vendored migration guide (codex-rs/skills/src/assets/samples/openai-docs/references/upgrading-to-gpt-5p6-sol.md:210) describes this exact failure mode:Its remedy, at line 242: "Place explicit breakpoints at the actual stable rendered boundary using
prompt_cache_breakpoint." And at line 244, why this now costs money: "Cache writes cost more than ordinary uncached input, so a lower hit rate can be both slower and more expensive."Codex cannot emit the field.
ContentItem::InputTextandInputImagehave noprompt_cache_breakpointmember or extension map, so the documented field is unrepresentable in both HTTP and WebSocket request construction.In a controlled replay of real Codex request bodies with the same ~9k-token developer prefix, a fixed
prompt_cache_key, and only the final user message changed, unmodified requests produced 0% cache hits and rewrote ~9,060 tokens each. Adding onlyprompt_cache_breakpoint: {"mode":"explicit"}to the final stableinput_textblock produced 98.6% hits and reduced warm-request writes to 123–128 tokens.This does not affect every turn. Append-only continuation turns within a single session already reached ~98% hits. The demonstrated failure is reuse of a stable startup prefix when independent requests diverge at the first volatile user turn.
The source-level defect is the missing content-block field. Session-scoped cache keys are a separate compounding issue on the measured backend, automatic breakpoint placement is a policy decision, and
prompt_cache_optionsis optional API completeness.Environment
0.145.0and0.146.0-alpha.10, tested as of 2026-07-24rust-v0.145.0(25af12f7e615),rust-v0.146.0-alpha.10(ae83c6df6ffd), andmain@4c43465133428898aa84f0bfc02c306ed65fb66agpt-5.6-solgpt-5.6-terra,gpt-5.6-luna/openai/v1/responses)responses, streamingThe measurements below are from Bedrock Mantle. The serialization defect is backend-independent and directly verifiable from the Codex source, but absolute cache behavior and billing should also be reproduced against
api.openai.combefore being treated as universal.Expected behavior
OpenAI's prompt-caching guide documents that GPT-5.6 and later models can mark the exact end of a reusable prompt prefix by adding:
to a supported content block. Content after that block may then change without invalidating the earlier cached prefix.
Codex should at minimum be able to represent and serialize this field on supported content blocks. Whether Codex places breakpoints automatically, exposes them through configuration, or both is a separate default-policy decision. The vendored migration guide explicitly advises using explicit breakpoints only for measured stable boundaries and not converting every prompt globally.
Actual behavior
Codex always relies on the implicit managed breakpoint because it has no wire representation for an explicit one. When requests share a large startup prefix but differ at the first volatile user turn, Codex cannot mark the actual stable boundary.
On the measured workload, those requests missed and rewrote essentially the entire eligible prefix. In contrast, ordinary append-only turns within one session cached normally because each prior turn became stable prefix for the next.
Minimal source proof
The content-block field is unrepresentable
codex-rs/protocol/src/models.rs:702inrust-v0.145.0and:706on the citedmainrevision:There is no
prompt_cache_breakpointfield and no#[serde(flatten)]or extra-fields map. The API supports breakpoints oninput_text,input_image, andinput_file, but Codex cannot serialize one on any of those blocks.The top-level cache options are also absent
ResponsesApiRequestandResponseCreateWsRequestboth containprompt_cache_key, but neither containsprompt_cache_optionsor an arbitrary request-body extension map:This is not required for the measured hit-rate recovery: the A/B below used a content-block breakpoint without sending
prompt_cache_options. It is listed separately because the same GPT-5.6 API surface is incomplete in both the HTTP and WebSocket request structs.Configuration cannot work around the missing nested field
ModelProviderInfoaccepts connection-level customization such asbase_url,wire_api, headers, query parameters, and retry controls, but no request-body transformation. The schema usesadditionalProperties: false, so a user cannot add a nestedprompt_cache_breakpointthroughconfig.toml.A top-level request-body passthrough such as #34569 could expose
prompt_cache_options, but it would not solve the core bug unless it also supported structured transformation insideinput.Controlled reproduction
Procedure
model_providerusingwire_api = "responses".codex execrequest body.prompt_cache_key;usage.input_tokens_details.cached_tokensandcache_write_tokens.input_textblock in the stable developer prefix:The measured bodies used the non-lite request shape because the production gateway rejects the proprietary
additional_toolsitem. The serialization gap is the same in the Responses Lite path; only the correct placement differs.Result
The first request in each arm was a cold write. The table below compares the three subsequent requests.
No
prompt_cache_optionswere sent. The only request-body change was the content-block breakpoint.Scope and non-claims
prompt_cache_keyas a routing and matching hint, not a guaranteed hard cache partition.Suggested minimum fix
ContentItem::InputTextandInputImage, withskip_serializing_if = "Option::is_none". AddInputFilesupport when that content variant exists.supports_prompt_cache_breakpoints. Do not infer support solely from a model-name string, because custom providers may expose different request capabilities.Follow-up improvements that are useful but not required to establish or fix the core serialization defect:
prompt_cache_options: Option<PromptCacheOptions>to both HTTP and WebSocket request structs;CompactionInput, which also carriesprompt_cache_keywithoutprompt_cache_options, after confirming whether the specialized compact endpoint accepts the field.Additional measurements
Intra-session continuation already caches correctly
Five sequential turns from one unmodified Codex session:
This is why the issue is easy to miss when inspecting a long interactive session: the implicit tail breakpoint works for monotonically growing history.
Cross-session key interaction on Bedrock Mantle
Three captured
codex execbodies contained 5,621 / 5,641 / 5,617 input tokens and differed in their volatile tails. Each arm replayed those same fixed bodies.prompt_cache_keyonlyThe inputs total 16,879 while the full-miss writes total 16,873 because the provider reported two fewer write tokens than total input on each request.
This establishes that, on the measured backend, a stable key was also needed for independent sessions to realize reuse. It is a compounding key-policy issue, not evidence that the missing breakpoint field itself is backend-specific.
Other causes tested
stream: trueandstream: falsebehaved the same.lowthroughxhighshowed the same baseline miss pattern.reasoning.encrypted_contenthad no measurable effect after the arms were pre-warmed identically.Request shapes and breakpoint placement
All three shipped GPT-5.6 catalog entries have
use_responses_lite: true, but custom providers may use the non-lite path.Responses Lite
Codex prepends an
additional_toolsdeveloper item and a developer message containing base instructions toinput; top-levelinstructionsandtoolsare omitted.{ "model": "gpt-5.6-sol", "prompt_cache_key": "<session UUID>", "input": [ {"type": "additional_tools", "role": "developer", "tools": ["..."]}, {"type": "message", "role": "developer", "content": [ {"type": "input_text", "text": "..."} ]}, {"type": "message", "role": "developer", "content": [ {"type": "input_text", "text": "..."}, {"type": "input_text", "text": "..."} ]}, {"type": "message", "role": "user", "content": ["..."]} ] }additional_toolshas no content block on which to place a breakpoint. A placement algorithm would need to mark the last supported content block in the measured stable developer portion, not theadditional_toolsitem itself.Non-lite Responses
instructionsandtoolsremain top-level, while leading developer messages are ininput:{ "model": "gpt-5.6-sol", "prompt_cache_key": "<session UUID>", "instructions": "...", "input": [ {"type": "message", "role": "developer", "content": [ {"type": "input_text", "text": "..."}, {"type": "input_text", "text": "..."} ]}, {"type": "message", "role": "user", "content": ["..."]} ], "tools": ["..."] }The controlled A/B marked the last
input_textblock in the stable developer item. An implementation must not assume every developer-role item is stable: current time, workspace data, permissions, tool availability, and project instructions can vary. The boundary should be based on the rendered stable portion, splitting content blocks where necessary.Cost impact and provider billing evidence
GPT-5.6 cache writes are billed at 1.25× uncached input, while cache reads are billed at 0.10×. A write therefore costs 12.5× a read. Earlier model families did not expose the same cache-write line-item cost.
AWS Cost Explorer for
OpenAI GPT-5.6 Sol (Amazon Bedrock Edition), 2026-07-01 through 2026-07-23 complete days, filtered toRECORD_TYPE = Usage:cache_write_tokens_30m_standardoutput_tokens_standardinput_tokens_standardcache_read_tokens_standardCache-write tokens were 9.1× cache-read tokens. Counting uncached input, cache writes, and cache reads together gives a 9.6% aggregate cached-token share.
For comparison, the same client and gateway against
OpenAI GPT-5.5 (Amazon Bedrock Edition)billed 9.17M input tokens and zero cache-write tokens over the same period.What this evidence establishes:
What it does not establish:
Applying the controlled reductions to all invoice writes would imply approximately $1,157 at the 65% cross-session reduction or $1,745 at the 98% same-prefix A/B reduction. Those are illustrative upper bounds, not attributable savings.
Relationship to existing issues
ModelProviderInfohas no generic top-level request-body extension. It could exposeprompt_cache_options, but not a nested content-block breakpoint without structured input transformation.use_responses_litechanges the rendered prefix shape and therefore where an automatically inserted breakpoint would belong.No issue found in the searched set reports that
prompt_cache_breakpointis structurally unserializable in Codex, and no open PR found in that search addresses it.Verification notes
rust-v0.145.0,rust-v0.146.0-alpha.9,rust-v0.146.0-alpha.10, and the citedmainrevision. Relevant line numbers move, but the missing fields remain.git grep -l -E 'prompt_cache_options|prompt_cache_breakpoint' <ref>returned only the vendored migration-guide markdown asset and no Rust implementation at the checked refs.ResponsesApiRequestchanged between stable and prerelease for unrelated tool serialization, confirming the request struct has been edited recently without adding these cache fields.RECORD_TYPE = Usageand grouping byUSAGE_TYPE.UsageQuantityis reported in millions of tokens.