openai / openai/codex

Forks and fresh sessions lose reported cache reuse; session-id affects reuse independently of prompt_cache_key

Open
#44,716 0 comments 5 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug CLI session
Dominant language
Rust
Stars
125k
Forks
19.4k
PR merge metrics
PR metrics pending

Description

Upstream confirmation — September 17 update

Merged upstream PR #44862 (September 11, merge commit bc5957eac9e89e66f990ed490d11e625a4a3b02c) explicitly documents that ChatGPT derives Responses cache affinity from the session-id header. Its implementation uses the effective prompt cache key for root-agent HTTP and WebSocket session headers, while retaining actual session identity in metadata. This supplies upstream confirmation of the cache-affinity mechanism implicated by the experiments below.

The automatic parent-key inheritance in that change is explicitly limited to ephemeral root forks (config.ephemeral), not all persistent user forks or fresh sessions. It also preserves enabled goal tool definitions on ephemeral forks to avoid changing the tool prefix. This is a merged source change, not a verified deployment or a live post-fix cache benchmark. The individual response IDs below have not received a backend trace interpretation, so physical recomputation versus reported credit is still unproven.

The experimental record below predates that change; its requests for mechanism confirmation should be read with this update. The issue remains open because the merged change does not establish coverage for ordinary persistent forks.

Summary

Forks and fresh sessions can lose reported cache reuse for inherited or repeated context on the ChatGPT-authenticated Codex backend. Controlled experiments implicate HTTP/WebSocket-handshake session-id independently of JSON prompt_cache_key. Preserving only the parent's JSON key is not a demonstrated fix, and a fork does not invariably miss.

The strongest first-fork experiment retained only the parent's HTTP session header while leaving the child's entire body and own cache key unchanged: it reused 40,064 / 41,565 input tokens, before any sibling sent a model request. Native and parent-key-only siblings each reused 12,928 / 41,565. The parent remained warm throughout.

This establishes an effect on reported cached-input usage, not the underlying server mechanism or physical KV-cache recomputation. Backend trace interpretation is requested below. /side is in the original reported scope but was not separately exercised through its UI.

Environment and method

  • Installed codex-cli 0.154.0, macOS arm64, existing ChatGPT authentication; no public API-key experiment.
  • Valid controlled comparisons requested and returned gpt-6-astra, low reasoning, returned service tier default.
  • Real sessions and forks used app-server JSON-RPC with long synthetic reference prompts. HTTP request interventions used a loopback forwarding proxy; direct WebSocket controls used the authenticated Codex responses endpoint and responses_websockets=2026-02-06.
  • Frozen replays kept complete request bodies/settings fixed except the named intervention, did not append answers, and did not use previous_response_id. Usage came from upstream response.completed; the initial app-server benchmark used per-request tokenUsage.last, not inherited cumulative totals.
  • Later fork/WebSocket batches spaced request starts at least ten seconds apart. Other account traffic and backend load were not controlled.
  • Captured credentials and full requests remained in memory. Saved evidence includes response IDs, usage, hashes, and structural comparisons. No product code or global configuration was changed.
  • Source references are pinned to da20788df913189878ebca7f4963d8a363ee6bf2; tests used the installed binary, not a rebuilt checkout.

Measured results

Initial real-fork benchmark

Two forks were initialized from the same parent snapshot before the parent control prompt.

Request Input tokens Cached tokens
Parent cold 35,068 0
Parent warm 40,281 34,944
Parent control 40,299 40,064
Fork 1 first 41,951 0
Fork 1 follow-up 41,969 41,728
Fork 2 first 41,951 0
Fork 2 follow-up 41,969 41,728
Parent return 40,317 40,064

This reproduced first-fork reported misses, but child inputs were 1,652 tokens larger and full requests were not captured in this initial benchmark, so it did not isolate causation.

Frozen-key, fresh-session, and identity controls
  • Frozen fork replays with original, parent, and never-used random JSON cache keys all reused 36,608 / 36,818 input tokens. Frozen parent replays with original and never-used keys both reused 34,944 / 35,166. Bodies excluding the key and forwarded headers were identical within each group. The fork body was already warm: this does not prove that using the parent's key prevents the initial miss.
  • Three actual fresh sessions initially reported zero cached tokens, then exact replays reused 34,944. The first session had 35,059 input tokens; the other two had 35,119. Their full bodies differed in input, metadata, and cache key, so equal user prompts were not complete-input controls.
  • With frozen 35,059-token input, two fresh identity-bundle interventions retaining the original JSON key yielded 0 and 7,936 cached tokens; key-only interventions retained the 34,944 baseline. The bundle changed body identity metadata and five identity headers together. Later both-changed requests reused already-warmed identities and are not independent cold controls.
  • Narrowed single-field tests on 35,059 input tokens: changing only HTTP session-id yielded 21,376 cached tokens in each of two fresh trials. Changing only JSON key, body thread metadata, or HTTP thread-id retained 34,944. Session-only body hashes were identical, with no other header changes.
  • Randomized header interaction trials were variable: some fresh identities fully hit, and one final unchanged baseline returned zero. Stable identity therefore does not guarantee reuse, nor does changed identity guarantee a miss.
Paced interventions before the child's first request

Children were initialized from the same parent snapshot. Model, reasoning, and instructions were checked before forwarding. Parent return controls replayed the exact frozen parent request, without growing its history.

Cohort A: six forks, randomized treatment order Cached tokens
Native child identity/key 0; 0
Only JSON key replaced with parent's key 12,928; 0
Only HTTP session-id replaced with parent's value 40,064; 41,344

Parent: 40,295 input / 40,064 cached. Every child: 41,563 input. All six parent returns retained 40,064 cached tokens. The later 41,344 result may benefit from an earlier sibling warming shared context; it cannot be attributed exclusively to the parent.

Cohort B used a new parent and unique synthetic prompt nonce, and ran the session-header intervention before any sibling model request.

Cohort B, execution order Input Cached
Parent control 40,297 40,064
Child 1: only parent HTTP session-id; child's own JSON key retained 41,565 40,064
Child 2: native identity/key 41,565 12,928
Child 3: only parent's JSON key 41,565 12,928

All three interleaved parent returns retained 40,064 cached tokens. Child 1's before/sent body hashes were identical; its sole changed header was session-id. Ignoring item id fields, the first 12 parent/child input items were identical. The child added a 5,665-byte developer item before the final user message, increasing reported input by 1,268 tokens. Ignoring IDs is a structural comparison technique, not an assertion about backend ID semantics.

WebSocket controls

Direct replay of a captured real HTTP request over WebSocket, not a native TUI WebSocket fork test. All responses had 35,118 input tokens, with full inputs/settings fixed except the stated treatment.

Treatment, execution order Cached tokens
Original socket after HTTP seed 34,944
Exact repeat, same socket 34,944
New JSON cache key, same socket 34,944
New body identity metadata, same socket 34,944
New socket, unchanged identity/body 34,944
New socket, only fresh handshake session-id 0
Another socket, repeat that new session-id 34,944
New socket, fresh identity-header bundle 21,376
New socket, change other identity headers but retain original session-id 34,944
New socket, original baseline 34,944

Reconnecting alone did not reproduce the session-header intervention's miss. A diagnostics request returned Unsupported parameter: prompt_cache_options. Earlier HTTP comparison-diagnostics attempts returned 400 without an explanatory message captured.

Source findings

Limitations and excluded experiments

  • The original cache-key-only explanation and proposed key-inheritance fix were too strong. Later controls contradict deterministic key-only causation. Neither key inheritance nor parent-session-header reuse is a validated production fix.
  • An initial screening response changed reported input count despite unchanged body. An assertion fired before its counters were saved; that unknown numeric result is excluded. Later harness versions saved anomalous responses before consistency checks.
  • One early paced fork cohort omitted explicit fork configuration, returning Astra for the parent and Sol for children. The entire cross-model cohort is excluded. Corrected cohorts explicitly pinned and checked configuration. This harness error is not evidence that native TUI fork changes models.
  • Fast exploratory batches sometimes exceeded 15 requests/minute; overflow routing was a possible confound, not a demonstrated explanation for the unchanged-control miss. Later batches were paced.
  • Offline evidence verification passes for recorded findings and exclusions; this does not prove universal backend behavior. No separate /side UI run or full native TUI WebSocket fork run was performed.
  • Reported usage cannot establish physical recomputation. Official diagnostics documentation explicitly permits a key-related reported miss without a physical miss. Public API documentation does not establish every behavior of the Codex-authenticated gateway.

Expected outcome and maintainer questions

Preserve eligible inherited-prefix reuse where supported while maintaining independent conversation identities. The mechanism and supported fix require backend confirmation; blindly retaining a parent's session header may alter identity semantics or telemetry.

  1. Does the Codex gateway override or supplement prompt_cache_key using HTTP/handshake session-id for routing, cache accounting, or hidden-context selection?
  2. Can the response IDs below establish whether the native child physically recomputed the inherited prefix or merely lost cached-input credit?
  3. What supported mechanism preserves fork cache affinity without conflating conversation identities? Is a supported comparison-diagnostics interface available for ChatGPT-authenticated Codex requests?
Cohort B request Response ID
Parent control resp_0a22c55f2305c9b4016aa380d1635087d2b1d91fdbd31e8be2
Parent-session-header first child resp_0a22c55f2305c9b4016aa380db3f1487d2a6c0948c20fcf3b3
Native child resp_0a22c55f2305c9b4016aa380ef35f887d2aaa25d31d9e6d7df
Parent-key-only child resp_0a22c55f2305c9b4016aa3810327b887d28c56a504f30f3f9d

Related: #24704 concerns forked subagent cache lineage; this report concerns normal user forks and fresh sessions. The request-level effect is established on the tested paths; the server-internal explanation remains unresolved.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the cited session initialization in codex-rs/core/src/session/session.rs and the identity, handshake, and routing code in codex-rs/core/src/client.rs. Reproduce the documented HTTP and WebSocket controls before drawing conclusions. Done requires maintainer or backend confirmation of supported cache-affinity behavior and coverage for ordinary persistent forks, not just the merged ephemeral-fork change.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend, performance
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.