Skip to content

[Bug]: ChatGPT codex backend 400s on gpt-5.6 models whenever prompt_cache_retention is forwarded (and intermittently even without it) #2092

Closed

Description

@pumpkinredbean

Client or integration

Codex App

Area

Provider adapter

Summary

Requests to gpt-5.6-luna / gpt-5.6-sol through the openai provider (ChatGPT codex backend) fail with 400 whenever the inbound request carries prompt_cache_retention. The remote Codex App started emitting this parameter on some turn requests (the string does not exist anywhere in codex-rs 0.146.0 — it is injected client-side), opencodex forwards request bodies opaquely, and one 400 aborts the whole agent turn mid-run.

Deterministic reproduction on stock 2.25.0 (proxy at localhost:10100, provider openai → https://chatgpt.com/backend-api/codex, authMode forward, account pool):

# prompt_cache_retention: "24h"      → 400 {"detail":"Unsupported parameter: prompt_cache_retention"}  (3/3)
# prompt_cache_retention: "in_memory" → 400 {"detail":"Unsupported parameter: prompt_cache_retention"}  (3/3)
# parameter omitted                   → 200 OK  (3/3)
# client-shaped (service_tier=priority, prompt_cache_key, client_metadata, reasoning.effort=max,
#   parameter omitted)                → 200 OK  (3/3)

Request-history confirms the 400s are real upstream round trips (~300-600ms, sendCount: 1, account main), not proxy-side validation.

Additional upstream inconsistency observed the same day on 2.11.1: the same error class also fired intermittently on requests whose wire body provably did not contain the parameter (verified by logging the serialized outbound body at the failure point: prompt_cache_retention=undefined, 21 failures / ~9,100 requests over ~2h on one busy session, clustered around compaction-heavy periods). Earlier that morning one deployment accepted "24h" (response echoed prompt_cache_retention:"24h"), while another rejected the same value allowlist-style hours later. So the backend appears to apply account-level cache defaults with per-deployment disagreements; the one invariant is that the parameter must never be sent.

Reproduction

  1. ocx start --port 10100 (provider openai → ChatGPT codex backend, forward auth, pool mode)
  2. curl -N -X POST http://127.0.0.1:10100/v1/responses -H 'Content-Type: application/json' -d '{"model":"gpt-5.6-luna","input":[{"type":"message","role":"user","content":[{"type":"input_text","text":"say ok"}]}],"prompt_cache_retention":"24h","store":false,"stream":true}'
  3. Observe {"detail":"Unsupported parameter: prompt_cache_retention"} (400)

In production the parameter arrives from the Codex App itself: threads on gpt-5.6 models intermittently die with Provider error 400: {"error":{"message":"prompt_cache_retention is not supported on this model","code":"unsupported_api_for_model"-class errors}}.

Version

2.25.0 (reproduced); also observed on 2.11.1. No reference to prompt_cache_retention exists in the 2.25.0 source — bodies are passed through verbatim.

Operating system

macOS 15.5 (Apple Silicon)

Provider and model

openai (ChatGPT codex backend) / gpt-5.6-luna, gpt-5.6-sol

Logs or error output

# Upstream response (stock 2.25.0, request carrying the parameter):
{"detail":"Unsupported parameter: prompt_cache_retention"}

# Client-facing failure observed on 2.11.1 (turn aborts mid-run):
Provider error 400: {"error":{"message":"prompt_cache_retention is not supported on this model","type":"invalid_request_error","param":"prompt_cache_retention","code":"invalid_parameter"}}

# Outbound-body log at the moment of one such 400 (note: parameter absent from the wire):
wire prompt_cache_retention=undefined model=gpt-5.6-luna store=false
keys=model|input|tool_choice|parallel_tool_calls|reasoning|store|stream|stream_options|include|service_tier|prompt_cache_key|text|client_metadata

Redacted configuration

{
  "providers": {
    "openai": {
      "adapter": "openai-responses",
      "baseUrl": "https://chatgpt.com/backend-api/codex",
      "codexAccountMode": "pool",
      "authMode": "forward"
    }
  }
}

Suggested handling / local workaround

Strip the parameter in the openai-responses passthrough adapter so a client-emitted value can never reach the wire (the backend still applies its own account-level cache default — responses keep echoing prompt_cache_retention:"24h"). This mirrors the existing stripUnsupportedReasoningParams / issue-#323 precedents. I have this running locally with the reproduction matrix going 400 → 200 across the board; happy to open a PR against dev with a focused regression test.

+function stripUnsupportedPromptCacheRetention(body: unknown): unknown {
+  if (!isPlainObject(body)) return body;
+  if (!Object.hasOwn(body, "prompt_cache_retention")) return body;
+  const { prompt_cache_retention: _retention, ...rest } = body;
+  return rest;
+}

(wired into the outBody pipeline in createResponsesPassthroughAdapter.buildRequest)

Optionally also consider treating this specific 400 as one-time retryable: during the intermittent window, identical param-less requests alternately failed and succeeded within minutes.

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. coderabbitai commented on Aug 19, 2026

    @coderabbitai
    Contributor
    🔗 Related PRs

    #304 - fix(kiro): restore hardening and complete text turns [open]
    #489 - fix(responses): strip max_output_tokens and metadata in forward mode [merged]
    #746 - fix(providers): route GitHub Copilot Responses-only models off chat completions [open]
    #757 - feat: route GPT-5.6 Pro through standard ChatGPT [open]
    #889 - fix(providers): Copilot mixed-wire routing, DeepSeek service_tier + reasoning replay, Claude 4.6/4.7 1M windows [open]


    📝 Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 Issue enrichment is currently in open beta.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. added
    bugSomething isn't working
    account-poolOAuth, credentials, Codex pool, quota, failover, plans
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    on Aug 19, 2026
  3. brotherjean commented on Aug 19, 2026

    @brotherjean

    Additional redacted observation from another macOS Apple Silicon setup:

    • gpt-5.6-terra and gpt-5.6-sol failed through the native openai route while gpt-5.6-luna remained usable.
    • The local installation was OpenCodex 2.15.1; this was not independently reproduced on 2.25.0.
    • I could not verify whether prompt_cache_retention was present in the failing wire body, so this is an additional symptom, not a claim about the root cause.

    Could the reproduction matrix include Terra as well as Luna/Sol and compare the forwarded request fields? No keys, tokens, OAuth data, or personal identifiers are included here.

  4. yzxcj797 commented on Aug 19, 2026

    @yzxcj797
    Contributor

    Fixed, model-scoped — and there's a nuance worth surfacing: main already pins the opposite behavior for another model. tests/openai-responses-passthrough.test.ts:807 asserts prompt_cache_retention is preserved in the forward body for gpt-5.5 — matching the issue's own observation that the backend accepted "24h" and echoed it back on some deployments. A blanket strip would silently drop a parameter a deployment honors.

    So the strip is scoped to the ids the report proves reject it:

    • stripPromptCacheRetentionForGpt56 runs on the forward path after the existing unsupported-param strip and removes the field only when modelId starts with gpt-5.6 (luna/sol covered, and future 5.6.x variants);
    • gpt-5.5 and every other model keep the current preserve behavior — the existing pin stays green unchanged.

    Tests: the issue's repro shape (both 5.6 models, "24h" and the Codex-App field shape) asserting the field never reaches the wire, and the explicit non-gpt-5.6 keep case.

    (Couldn't run the bun suite locally on this Windows checkout; relying on CI.)

  5. pumpkinredbean commented on Aug 19, 2026

    @pumpkinredbean
    Author

    Correction to the report above (self-audit):

    1. The provenance claim was wrong. There is no evidence the Codex App emits prompt_cache_retention: the string does not exist in codex-rs 0.146.0, and none of the 42 recorded inbound request bodies on this proxy contain it. What remains verified: any request that does carry the parameter is forwarded verbatim and rejected (the deterministic matrix above), and the same error class also fired in production on requests whose wire body provably lacked the parameter. I don't know which client, if any, sends the parameter.
    2. The mangled log excerpt in "Logs or error output" should read "code":"invalid_parameter" (the unsupported_api_for_model fragment was pasted from an unrelated error).
    3. "The one invariant is that the parameter must never be sent" is overbroad — it holds for gpt-5.6 models on this backend, not universally. The model-scoped strip in fix(responses): never send prompt_cache_retention to gpt-5.6 models #2099 (preserve for gpt-5.5 per the existing test pin) is the right shape; please disregard my blanket-strip diff.
    4. "Clustered around compaction-heavy periods" was speculation; no correlation with compactions was verified.

    The deterministic reproduction and the param-less intermittent failure observations stand as reported.

  6. lidge-jun commented on Aug 19, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 64 / 80

    ChatGPT Codex 백엔드로 가는 gpt-5.6-luna/gpt-5.6-sol 요청에 prompt_cache_retention이 있으면 항상 400이다. Codex App이 클라이언트에서 이 필드를 넣고, OpenCodex는 바디를 그대로 넘긴다. 파라미터가 없으면 200이다. 기본 native 경로가 앱 한 필드 때문에 턴 전체가 죽어서 64다. 설치/재부팅 게이트는 아니라 80은 아니다. 사용자가 앱 바디를 못 고치므로 우회가 거의 없다.

    src/adapters/openai-responses.ts의 패스스루는 이미 비슷한 필드를 지운다. stripUnsupportedForwardParams()가 max_output_tokens와 metadata를 제거한다. 주석도 ChatGPT allowlist에 없는 필드를 말하는데, prompt_cache_retention은 그 목록에 없다. 이 함수는 패스스루 직전 outBody = stripUnsupportedForwardParams(outBody)로 호출된다. 현재 dev에서 prompt_cache_retention 문자열은 소스에 없다. 리포터 말대로 불투명 전달이다.

    src/responses/schema.ts는 prompt_cache_key만 optional로 두고 retention은 파싱하지 않는다. 스키마 밖 필드는 객체 나머지에 남아 업스트림으로 간다. prompt_cache_key는 허용되고, retention만 거절되는 재현과 맞다. service_tier/client_metadata/reasoning.effort는 리포터 실험에서 200이었다.

    같은 파일의 다른 strip도 같은 패턴이다. stripUnsupportedReasoningParams(), stripUnsupportedReasoningSummaryDelivery(), sanitizeReasoningInputContent()가 Spark/서머리/라우티드 reasoning 400을 막는다. retention도 그 줄에 넣으면 된다. 계정 풀이나 모델 슬러그 문제가 아니다. 리포터의 request-history는 업스트림 왕복(~300-600ms, sendCount: 1)이다.

    파라미터 없이 난 간헐 400은 이 패치로 닫히지 않을 수 있다. 2.11.1에서 아웃바운드에 retention이 없는데도 같은 에러 클래스가 난 기록이 있다. 백엔드 배포/계정 캐시 기본값 불일치일 수 있다. 그 부분은 재현 로그가 더 필요하다. 확실한 불변은 “이 파라미터를 보내면 안 된다”는 쪽이다.

    해결방안

    stripUnsupportedForwardParams()에서 prompt_cache_retention을 max_output_tokens/metadata와 같이 삭제한다. ChatGPT authMode=forward 패스스루에만 적용하고, API 키 Responses 등 retention을 받는 경로는 건드리지 않는다. 값은 "24h"든 "in_memory"든 버린다. 스키마에 필드를 추가할 필요는 없다. 회귀는 리포터 curl 세 갈래면 된다. prompt_cache_retention 있음 → 400 없이 통과. 없음 → 지금과 같은 200. prompt_cache_key는 유지. 간헐 무파라미터 400은 이 이슈에서 원인 단정하지 말고, 같은 로그 키로 한 번 더 잡는다.

    이 댓글은 grok-bot이 작성했습니다

  7. added a commit that references this issue on Aug 20, 2026
    5904178
  8. lidge-jun commented on Aug 20, 2026

    @lidge-jun
    Owner

    Fixed on dev by #2138, merged as 81492fd.

    Verified present on the branch rather than assumed: git log origin/dev shows the merge commit above. These PRs target dev rather than main, so GitHub does not auto-close the linked issue — this closure is manual.

    It ships in the next release cut from dev. Reopen if you still see it after updating.

  9. added a commit that references this issue on Sep 17, 2026
    7394be5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansbugSomething isn't workingproviderProvider adapters, OpenAI-compat presets, upstream API quirks

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions