fix(codex): forward the caller client version upstream instead of a pinned default - #13708
Conversation
…ning 0.144.1 The Codex provider reported a hardcoded client version (DEFAULT_CODEX_CLIENT_VERSION) to the ChatGPT backend. Newer gated models reject older clients, e.g.: The 'gpt-6-astra' model requires a newer version of Codex. so the pinned value silently rots on every CLI upgrade, and a user on the latest CLI is still refused. CodexExecutor.buildHeaders also dropped the clientHeaders/model/health arguments that the base class accepts (base.ts:509), so the caller User-Agent never reached the version resolution. - codexClient.ts: add getCodexClientVersionFromHeaders(), which reads the version the caller reports in its User-Agent (codex_cli_rs/<v>, codex_exec/<v>) or a version header, validated against SAFE_HEADER_TOKEN_PATTERN. getCodexUserAgent() now takes an optional version override. - codex.ts: forward clientHeaders/model/health to super.buildHeaders(), and use getCodexClientVersionFromHeaders(clientHeaders) ?? getCodexClientVersion(). Falls back to the existing env override / default when the caller sends nothing.
|
Thanks for tracking down the real bug here — Two things we'll need before merging, which we'll add on your branch and credit you for:
One design note for discussion: the generic, unnamespaced |
…-aware buildHeaders Adds unit coverage for the caller-version forwarding introduced in this PR: real Codex CLI User-Agent parsing, the generic version header, missing/empty headers, non-Codex User-Agent, and CRLF/oversized injection attempts safely returning null/falling back to the default version. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
|
Both items addressed, plus a note on the design question. 1. Unit coverage — added in I re-verified the parsing and the version-identity logic locally against this branch — 11/11 2. Description corrected. You're right, and the original was wrong twice over. Worth recording while correcting it: the drift argument is no longer hypothetical. The pin reads 3. |
|
Follow-up on the description: it was not following the repo PR template, so I restructured it into Two things worth flagging from that pass:
I left On the |
|
Thanks @zeeshanhaque21 — merging via the release merge-train. Validated in local merge-train (mt-train8e) on the devbox @ train tip b38c9340f092c8852ecfa7f6e0f9592fd6f7fc35 with the 26 sibling PRs of the owner-approved file-size rebaseline batch: typecheck:core, file-size (rebaselined entry _rebaseline_2026_09_18_merge_train_8_frozen_growth), complexity, cognitive-complexity, changelog-integrity green; changed-area node:test 722/722; vitest 479/482 where the 3 reds were 5s/20s timeouts under devbox load 18 and pass when re-run alone on the same train tip (flake class). Merged --admin per merge-gates §7. |
Keep the emulated client fingerprint in lockstep with the CLI pinned in the Dockerfile: shared client constant, .env.example overrides, provider translate-path golden, executor header assertions, the caller-version fallback assertions from diegosouzapw#13708, and the live env/stealth docs.
Keep the emulated client fingerprint in lockstep with the CLI pinned in the Dockerfile: shared client constant, .env.example overrides, provider translate-path golden, executor header assertions, the caller-version fallback assertions from diegosouzapw#13708, and the live env/stealth docs.
Keep the emulated client fingerprint in lockstep with the CLI pinned in the Dockerfile: shared client constant, .env.example overrides, provider translate-path golden, executor header assertions, the caller-version fallback assertions from diegosouzapw#13708, and the live env/stealth docs.
Keep the emulated client fingerprint in lockstep with the CLI pinned in the Dockerfile: shared client constant, .env.example overrides, provider translate-path golden, executor header assertions, the caller-version fallback assertions from diegosouzapw#13708, and the live env/stealth docs.
Keep the emulated client fingerprint in lockstep with the CLI pinned in the Dockerfile: shared client constant, .env.example overrides, provider translate-path golden, executor header assertions, the caller-version fallback assertions from diegosouzapw#13708, and the live env/stealth docs.
Keep the emulated client fingerprint in lockstep with the CLI pinned in the Dockerfile: shared client constant, .env.example overrides, provider translate-path golden, executor header assertions, the caller-version fallback assertions from diegosouzapw#13708, and the live env/stealth docs.
Keep the emulated client fingerprint in lockstep with the CLI pinned in the Dockerfile: shared client constant, .env.example overrides, provider translate-path golden, executor header assertions, the caller-version fallback assertions from #13708, and the live env/stealth docs. Co-authored-by: backryun <backryun@daonlab.local>
…inned default (diegosouzapw#13708) * fix(codex): forward the caller client version upstream instead of pinning 0.144.1 The Codex provider reported a hardcoded client version (DEFAULT_CODEX_CLIENT_VERSION) to the ChatGPT backend. Newer gated models reject older clients, e.g.: The 'gpt-6-astra' model requires a newer version of Codex. so the pinned value silently rots on every CLI upgrade, and a user on the latest CLI is still refused. CodexExecutor.buildHeaders also dropped the clientHeaders/model/health arguments that the base class accepts (base.ts:509), so the caller User-Agent never reached the version resolution. - codexClient.ts: add getCodexClientVersionFromHeaders(), which reads the version the caller reports in its User-Agent (codex_cli_rs/<v>, codex_exec/<v>) or a version header, validated against SAFE_HEADER_TOKEN_PATTERN. getCodexUserAgent() now takes an optional version override. - codex.ts: forward clientHeaders/model/health to super.buildHeaders(), and use getCodexClientVersionFromHeaders(clientHeaders) ?? getCodexClientVersion(). Falls back to the existing env override / default when the caller sends nothing. * test(codex): cover getCodexClientVersionFromHeaders and clientHeaders-aware buildHeaders Adds unit coverage for the caller-version forwarding introduced in this PR: real Codex CLI User-Agent parsing, the generic version header, missing/empty headers, non-Codex User-Agent, and CRLF/oversized injection attempts safely returning null/falling back to the default version. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Zeeshan Haque <zeeshan@moonscape.local> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
… 0.159.2 Ports upstream diegosouzapw#13708: CodexExecutor.buildHeaders now forwards the clientHeaders/model/health arguments to BaseExecutor and reports the caller's own Codex version upstream — parsed from a codex_cli_rs/codex_exec User-Agent or a `version` header, validated against SAFE_HEADER_TOKEN_PATTERN — instead of a pinned default, so ChatGPT-account model gating follows the user's CLI. Also bumps DEFAULT_CODEX_CLIENT_VERSION 0.144.1 -> 0.159.2 (the GPT-6.1 Sol catalog requires >= 0.159.0 and the live Codex catalog gates models on the reported client version), pins the Dockerfile to @openai/codex@0.159.2, and refreshes the translate-path golden (codex UA/Version plus the stale Antigravity cli/ide fallbacks 1.1.5/2.1.1 -> 1.1.13/2.8.1).
Summary
The Codex provider reports a pinned client version to the ChatGPT backend. That value is declared
once, in
src/shared/constants/codexClient.ts(DEFAULT_CODEX_CLIENT_VERSION, currently"0.153.4",kept in lockstep with the root Dockerfile's
@openai/codexpin).open-sse/config/codexClient.tsre-exports it and wraps it with the
CODEX_CLIENT_VERSIONenv override, so every request carries thatvalue unless an operator sets the env var.
Newer gated models reject older clients, so a caller on the latest CLI is still refused:
"Upgrade" cannot help here — the caller's own version is never consulted. The pin has already drifted
again (
0.153.4here vs0.154.0on npm), and it rots on every CLI release.Fix: forward the version the caller actually reports, falling back to the env override / default
when it sends nothing.
open-sse/config/codexClient.ts— newgetCodexClientVersionFromHeaders()reads the version from thecaller User-Agent (
codex_cli_rs/<v>,codex_exec/<v>,codex-cli/<v>) or aversionheader,validated against the existing
SAFE_HEADER_TOKEN_PATTERN.getCodexUserAgent()gains an optionalversion override.
open-sse/executors/codex.ts—CodexExecutor.buildHeadersnow accepts and forwardsclientHeaders/model/healthtosuper.buildHeaders(); the override was dropping them, so thecaller User-Agent never reached version resolution. Uses
getCodexClientVersionFromHeaders(clientHeaders) ?? getCodexClientVersion().Related Issues
Validation
Change type: provider.
(
check:provider-consistency,check:provider-assets) not applicable: no provider catalog,registry, or dashboard asset changes in this PR.
npm run lint— not yet run.release/v3.8.51is its currenttip
c0f92ec9, so the branch sits directly on the active base. Focused checks rerun afterreconcile: pending.
npm run check:pr-test-policyreturnsResult: PASS(2 production files in scope, 1 test file).Manual verification:
getCodexClientVersionFromHeaders({user-agent: "codex_cli_rs/0.154.0 (Mac OS 26.6.2; arm64)"})->
"0.154.0"; returnsnullfor empty/absent headers so the fallback applies.gpt-6-astravia/v1/responsesreturnsresponse.createdinstead of the 400; previously it failed.Tests Added Or Updated
tests/unit/codex-client-headers.test.ts(new, 10 cases) — extractor behaviour (real CLI User-Agents,version-header precedence, absent/empty headers, non-Codex UA, CRLF injection in both the versionheader and the UA, the 32-char token limit) and the
clientHeaders-awarebuildHeaderspath(forwarding, fallback to
0.153.4, injection rejection).Coverage Notes
Both changed production files are covered by the new test file.
open-sse/config/codexClient.tsiscovered directly.
open-sse/executors/codex.tsis covered at the version-identity level — the two linesbuildHeadersuses to setVersionandUser-Agent— not at the level of full executor construction,which requires the complete
open-sseimport graph. Coverage in touched files does not decrease: thechange adds code paths that the new file exercises, and no existing behaviour is removed.
Reviewer Notes
versionheader fallback is generic and unnamespaced. Flagged in review as a possible collisionwith an unrelated
versionheader some client already sends. Left as-is per review — happy to scopeit to
x-omniroute-codex-versionor drop it entirely.DEFAULT_CODEX_CLIENT_VERSIONstays insrc/shared/constants/codexClient.tsand remains the fallback;this change only stops it from being the only value ever sent.
VersionandUser-Agentare now intentionally caller-dependent. Clients that report no version are unaffected.