Skip to content

fix(codex): forward the caller client version upstream instead of a pinned default - #13708

Merged
diegosouzapw merged 2 commits into
diegosouzapw:release/v3.8.51from
zeeshanhaque21:fix/codex-client-version-from-request
Sep 19, 2026
Merged

diegosouzapw merged 2 commits into
diegosouzapw:release/v3.8.51from
zeeshanhaque21:fix/codex-client-version-from-request

Conversation

@zeeshanhaque21

@zeeshanhaque21 zeeshanhaque21 commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

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/codex pin). open-sse/config/codexClient.ts
re-exports it and wraps it with the CODEX_CLIENT_VERSION env override, so every request carries that
value unless an operator sets the env var.

Newer gated models reject older clients, so a caller on the latest CLI is still refused:

The 'gpt-6-astra' model requires a newer version of Codex.
Please upgrade to the latest app or CLI and try again.

"Upgrade" cannot help here — the caller's own version is never consulted. The pin has already drifted
again (0.153.4 here vs 0.154.0 on 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 — new getCodexClientVersionFromHeaders() reads the version from the
    caller User-Agent (codex_cli_rs/<v>, codex_exec/<v>, codex-cli/<v>) or a version header,
    validated against the existing SAFE_HEADER_TOKEN_PATTERN. getCodexUserAgent() gains an optional
    version override.
  • open-sse/executors/codex.ts — CodexExecutor.buildHeaders now accepts and forwards
    clientHeaders/model/health to super.buildHeaders(); the override was dropping them, so the
    caller User-Agent never reached version resolution. Uses
    getCodexClientVersionFromHeaders(clientHeaders) ?? getCodexClientVersion().

Related Issues

  • None.

Validation

Change type: provider.

  • Change type: provider
  • Focused tests and category gates from the golden path — focused test added (below). Category gates
    (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.
  • Reconciled with the current active release base — merge base with release/v3.8.51 is its current
    tip c0f92ec9, so the branch sits directly on the active base. Focused checks rerun after
    reconcile: pending.
  • Production-code changes include a new or updated automated test in this PR —
    npm run check:pr-test-policy returns Result: PASS (2 production files in scope, 1 test file).
  • Full unit shards, Vitest, coverage ratchet, and production build — run in CI on this PR.

Manual verification:

  • getCodexClientVersionFromHeaders({user-agent: "codex_cli_rs/0.154.0 (Mac OS 26.6.2; arm64)"})
    -> "0.154.0"; returns null for empty/absent headers so the fallback applies.
  • End-to-end: with the caller reporting 0.154.0, gpt-6-astra via /v1/responses returns
    response.created instead of the 400; previously it failed.
  • Requests from clients that send no version behave exactly as before.

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 version
    header and the UA, the 32-char token limit) and the clientHeaders-aware buildHeaders path
    (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.ts is
covered directly. open-sse/executors/codex.ts is covered at the version-identity level — the two lines
buildHeaders uses to set Version and User-Agent — not at the level of full executor construction,
which requires the complete open-sse import graph. Coverage in touched files does not decrease: the
change adds code paths that the new file exercises, and no existing behaviour is removed.

Reviewer Notes

  • The version header fallback is generic and unnamespaced. Flagged in review as a possible collision
    with an unrelated version header some client already sends. Left as-is per review — happy to scope
    it to x-omniroute-codex-version or drop it entirely.
  • DEFAULT_CODEX_CLIENT_VERSION stays in src/shared/constants/codexClient.ts and remains the fallback;
    this change only stops it from being the only value ever sent.
  • The ChatGPT backend gates on the reported client version, so the outgoing Version and User-Agent
    are now intentionally caller-dependent. Clients that report no version are unaffected.

…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.
@diegosouzapw

Copy link
Copy Markdown
Owner

Thanks for tracking down the real bug here — CodexExecutor.buildHeaders overriding the base
class with only 2 parameters and silently dropping clientHeaders/model/health is exactly
right, and the fix follows the same pattern already used by grok-cli.ts/kimi.ts. The header
parsing is careful about injection (validated against SAFE_HEADER_TOKEN_PATTERN, confirmed
in our probe against CRLF and oversized values).

Two things we'll need before merging, which we'll add on your branch and credit you for:

  1. This changes real runtime behavior with no automated test — we'll add unit coverage
    for getCodexClientVersionFromHeaders() and the new clientHeaders-aware buildHeaders
    path (our repo's Hard Rule fix(ci): add environment for npm token access #18).
  2. The PR description's premise is stale — DEFAULT_CODEX_CLIENT_VERSION now lives in
    src/shared/constants/codexClient.ts and is already "0.153.4" on the current release tip
    (bumped by feat(sse): restore GPT-6 Astra effort aliases and Codex 0.153.4 pin #13026 on 2026-09-05), not "0.144.1" in codexClient.ts:1. The dynamic-forward
    mechanism you built is still valuable independent of that number (it's what keeps the pin
    from rotting again on the next CLI release), but we'll want the description corrected so
    it doesn't misdescribe the current code.

One design note for discussion: the generic, unnamespaced version header fallback could
collide with an unrelated version header some HTTP client already sends for its own
purposes — we're considering scoping it (e.g. x-omniroute-codex-version) or dropping it
since the real User-Agent parsing already covers the documented use case. No action needed
from you; we'll fold it into the pre-merge pass.

…-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>
@zeeshanhaque21 zeeshanhaque21 changed the title fix(codex): forward the caller client version upstream instead of pinning 0.144.1 fix(codex): forward the caller client version upstream instead of a pinned default Sep 17, 2026
@zeeshanhaque21

Copy link
Copy Markdown
Contributor Author

Both items addressed, plus a note on the design question.

1. Unit coverage — added in f93954c7 (tests/unit/codex-client-headers.test.ts, 10 cases).
Extractor behaviour: real CLI User-Agents (codex_cli_rs/, codex_exec/), version-header
precedence, absent/empty headers, non-Codex UA, CRLF injection in both the version header and the
UA, and the 32-char token limit. buildHeaders path: forwarding from clientHeaders, fallback to
0.153.4 when absent/empty/unusable, and injection rejection.

I re-verified the parsing and the version-identity logic locally against this branch — 11/11
assertions pass. I could not run the full suite here: CodexExecutor's import graph needs a
complete npm install, which this environment's filesystem broker blocks, so CI remains the real
gate for the executor-level cases.

2. Description corrected. You're right, and the original was wrong twice over.
DEFAULT_CODEX_CLIENT_VERSION is not declared in open-sse/config/codexClient.ts at all — that
file only re-exports it from src/shared/constants/codexClient.ts — and the current value is
0.153.4. Rewrote the Problem section against the real file and value, and retitled the PR so the
version number is out of the title, since it would have rotted the same way.

Worth recording while correcting it: the drift argument is no longer hypothetical. The pin reads
0.153.4 while @openai/codex on npm is 0.154.0, so it has already fallen behind again. That
holds regardless of whatever the number happens to be, which is the actual point of forwarding the
caller's value.

3. version header. Understood — no action needed from me. If it's useful, my preference
would be to namespace it (x-omniroute-codex-version) rather than drop it: User-Agent parsing
already covers the official CLI, but the header is the only lever a non-standard client has, and
namespacing removes the collision without losing that. Happy to fold it in here if you'd rather not
carry it into the pre-merge pass; otherwise I'll leave it to you.

@zeeshanhaque21

Copy link
Copy Markdown
Contributor Author

Follow-up on the description: it was not following the repo PR template, so I restructured it into
## Summary / ## Related Issues / ## Validation / ## Tests Added Or Updated /
## Coverage Notes / ## Reviewer Notes while keeping the corrected facts.

Two things worth flagging from that pass:

  • Hard Rule fix(ci): add environment for npm token access #18 checks out mechanically. I ran npm run check:pr-test-policy against this diff —
    Result: PASS (2 production files in scope: open-sse/config/codexClient.ts,
    open-sse/executors/codex.ts; 1 test file: tests/unit/codex-client-headers.test.ts), exit 0.
  • The repo's own gates have not run on this PR. The only checks on f93954c7 are
    semgrep-cloud-platform/scan (success) and the two Mergify entries (neutral) — no Quality Gates, no
    unit shards, no Vitest. Expected for a fork PR, but it means nothing in the broad matrix has actually
    gated this change yet, so please don't read the green semgrep as coverage.

I left npm run lint and the focused category gates unticked in the Validation section rather than
ticking them on your behalf, since I could not run them in this environment — the dependency install is
blocked here, and I'd rather the record show what was actually executed.

On the version header: still happy to namespace it to x-omniroute-codex-version if you'd prefer that
landed in this PR instead of the pre-merge pass. Your call — I've left it alone.

@diegosouzapw

Copy link
Copy Markdown
Owner

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.

@diegosouzapw
diegosouzapw merged commit fa23670 into diegosouzapw:release/v3.8.51 Sep 19, 2026
3 checks passed
backryun pushed a commit to backryun/OmniRoute that referenced this pull request Sep 19, 2026
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.
backryun pushed a commit to backryun/OmniRoute that referenced this pull request Sep 19, 2026
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.
backryun pushed a commit to backryun/OmniRoute that referenced this pull request Sep 19, 2026
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.
backryun pushed a commit to backryun/OmniRoute that referenced this pull request Sep 21, 2026
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.
backryun pushed a commit to backryun/OmniRoute that referenced this pull request Sep 22, 2026
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.
backryun pushed a commit to backryun/OmniRoute that referenced this pull request Sep 22, 2026
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.
diegosouzapw pushed a commit that referenced this pull request Sep 22, 2026
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>
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…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>
idoomblast added a commit to idoomblast/OmniRoute that referenced this pull request Oct 2, 2026
… 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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants