Skip to content

feat(codex): sync the Codex client surface to openai/codex 0.148.0 - #403

Merged
amondnet merged 5 commits into
mainfrom
amondnet/codex-r
Aug 19, 2026
Merged

feat(codex): sync the Codex client surface to openai/codex 0.148.0#403
amondnet merged 5 commits into
mainfrom
amondnet/codex-r

Conversation

@amondnet

@amondnet amondnet commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What

Periodic audit of upstream openai/codex against the client-identity + Responses wire surface shunt mirrors. Range audited: rust-v0.144.4...rust-v0.148.0 (1286 commits, upstream stable released 2026-08-18).

Audit caveat worth recording: the GitHub compare API caps .files at 300 (alphabetical), which over a range this wide silently drops codex-rs/core/** and codex-rs/codex-api/** — i.e. exactly the files that matter. Each surface file was fetched at both tags and diffed locally instead.

Verified unchanged

WS beta still responses_websockets=2026-02-06 · codex-api/src/endpoint/responses.rs byte-identical · DEFAULT_ORIGINATOR still codex_cli_rs · user-agent format unchanged · version header still the built-in openai provider's CARGO_PKG_VERSION · ResponseCreateWsRequest wire fields unchanged.

Changed here

  1. Pinned identity 0.144.4 → 0.148.0. A pure literal bump. The minimal_client_version floor (0.144.0) is a different, server-side number and is deliberately untouched. user-agent = codex_cli_rs/0.148.0, version = bare 0.148.0.

  2. New x-codex-routing-hint header (X_CODEX_ROUTING_HINT_HEADER / build_routing_hint_header, codex-rs/core/src/client.rs). Value model=<upstream_model>, plus ;tier=<tier> when a service tier is on the wire. ChatGPT-OAuth arm only, mirroring upstream's suppression for api-key/bearer/aws providers. HTTP request + WS handshake.

  3. cache_write_tokenscache_creation_input_tokens. Upstream's SSE layer now reads usage.input_tokens_details.cache_write_tokens; shunt previously hardcoded cache_creation_input_tokens: 0. Both detail fields are peeled off the total with saturating arithmetic, preserving the invariant that the three input fields sum to the upstream input_tokens.

Review round — a defect this PR introduced, and fixed

The first commit shipped (2) by mirroring upstream's construction. That was wrong in a way worth stating plainly: upstream builds that hint from its own local config; shunt builds it from a network-facing client string. route.upstream_model is the client's raw model on prefix-route and default-provider routes (routing.rs, only a trailing [1m] stripped), so the first commit interpolated untrusted input into a header value.

Three reachable consequences, all closed in 3057d5a:

  • Pool-wide cooldown (the serious one). A control character in model made HeaderValue rejection surface as a deferred reqwest builder error at .send(). retry.rs classifies that non-transient, and pool.rs charges it to the account as a 30s "transport" cooldown. The error is deterministic, so it repeated per account — one malformed client string could cool the entire Codex OAuth pool before returning 502.
  • Tier forgery. model containing ; forged a ;tier= segment the body never sent, falsifying the function's own doc comment.
  • Unbounded header. model has no length bound, so neither did the header.

routing_hint now returns Option<HeaderValue>; both sites omit rather than fail — the shape upstream uses and the one this repo already uses for the equally client-derived x-gateway-model (stamp_gateway_headers). The slug guard is a positive allowlist, not a denylist of separators: HeaderValue admits every visible ASCII byte plus TAB, so enumerating metacharacters against a grammar shunt cannot observe is a guard that erodes. Charset verified against every model-id-shaped literal in config, discovery, and docs — 120 candidates, 0 rejected. Omission is logged at debug only (client-triggerable → higher levels are a log-flood vector) and never logs the model string, only its length.

Verification

cargo fmt --all --check clean · cargo clippy --all-targets --all-features -- -D warnings clean · 1953 passed, 0 failed across 19 binaries.

New tests were mutation-checked, not merely observed green. One mutation exposed a vacuous test of my own: the case "gpt-5,tier=priority" contains both , and =, and rejects on the = before the , rule is ever reached — so widening the allowlist by one character could never redden it. Every reject case now carries exactly one out-of-allowlist character, so a one-byte widening reddens exactly one case and names it; the realistic whole-string forges are asserted separately.

Live-probed against the real ChatGPT backend: the 0.148.0 identity is accepted (no Model not found), x-codex-routing-hint is accepted, and cache_write_tokens is present in the live response.completed usage.

Honest limit on (3): the field read 0 on both probe runs, as did cached_tokens, with an identical 2658-token prefix and shared prompt_cache_key seconds apart — caching never engaged, so a nonzero value was never observed end to end. The mapping targets a confirmed-real field and its arithmetic is unit-tested, but it is currently inert in practice. Absent or zero, the emitted usage is byte-identical to before, guarded by a regression test.

Known issues not caused by this PR

  • gateway::spend::persist::tests::malformed_typed_limit_is_hidden_without_losing_valid_limits is flaky — its capture_logs helper installs a per-test tracing subscriber that races under full-suite parallelism, returning an empty buffer. Last touched by feat(admin): add read/write admin keys and move the spend surface to [server.spend] #389 and feat(gateway): add spend-limit admin API (stage 1) #333, both already on main; this branch touches no gateway or tracing code. It may intermittently redden CI here. Deserves its own issue; deliberately not "fixed" by weakening it.
  • docs/m7-codex-websocket.md §2/§5 still reference src/adapters/codex_ws.rs, but that code now lives under src/adapters/responses/. Pre-existing, left alone.

Not done here (surfaced by the same audit)

  • codex_rollout_budget_units — new upstream usage field; did not appear in the live probe.
  • New SSE error code misalignment_policy_violation.
  • safety_buffering now also arrives via a response.metadata event (shunt implements none of it).
  • WS x-models-etag relocated into the codex.response.metadata event — shunt never consumed it, so no action.
  • Pre-existing: shunt parses neither x-codex-bengalfox-* nor x-codex-credits-*.

Docs

docs/codex-configuration.md §4.4 + §10 · docs/m7-codex-websocket.md §4 · docs/running.md · docs/m11-inbound-codex-endpoint.md · site/.../guides/codex.mdx and effort-and-context.mdx across en/ko/ja/zh-cn. wiki/ untouched (generated).

The inbound Codex passthrough is unaffected: it relays a caller's own x-codex-* headers verbatim through a separate send path, so there is no duplicate-header risk — and the tier-honesty property is scoped in the docs to the headers shunt builds.

Audit of upstream `rust-v0.144.4...rust-v0.148.0` (1286 commits) against the
surface shunt mirrors. Three changes:

- Bump the pinned Codex CLI identity 0.144.4 -> 0.148.0. Verified upstream:
  `DEFAULT_ORIGINATOR` is still `codex_cli_rs`, the `version` header is still
  the built-in openai provider's `CARGO_PKG_VERSION`, and the user-agent format
  is unchanged, so this is a pure literal bump. The `minimal_client_version`
  floor (0.144.0) is a separate, server-side number and is left alone.

- Send `x-codex-routing-hint`, new in this range (`X_CODEX_ROUTING_HINT_HEADER`
  / `build_routing_hint_header`, codex-rs/core/src/client.rs): the upstream
  model slug plus `;tier=<tier>` when a service tier is on the wire. Sent only
  on the ChatGPT OAuth arm, mirroring upstream's suppression for
  api-key/bearer/aws providers. The tier predicate mirrors the request body's,
  so the hint can never advertise a tier the body omitted. The websocket
  transport carries it as a handshake header, as upstream does.

- Map `usage.input_tokens_details.cache_write_tokens` (new upstream field) to
  Anthropic `cache_creation_input_tokens`, which shunt previously hardcoded to
  0. Both detail fields are now peeled off the total with saturating
  arithmetic, preserving the invariant that the three input fields sum to the
  upstream `input_tokens`. When the field is absent the emitted usage is
  byte-identical to before.

The upstream WS beta value (`responses_websockets=2026-02-06`), the Responses
endpoint, and the request payload shape are all unchanged in this range.

Verified against the live ChatGPT backend: the 0.148.0 identity is accepted
(no `Model not found`), `x-codex-routing-hint` is accepted, and
`cache_write_tokens` is present in the real `response.completed` usage.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the pinned Codex CLI client version from 0.144.4 to 0.148.0 across documentation and code. It introduces support for the x-codex-routing-hint header on both HTTP and WebSocket transports for the ChatGPT OAuth path. Additionally, it refines token usage tracking by extracting cache_write_tokens from OpenAI's input_tokens_details and mapping them to Anthropic's cache_creation_input_tokens to ensure accurate context calculations. Feedback is provided to refactor the routing_hint helper function in src/adapters/responses/request.rs to improve conciseness and readability.

Comment thread src/adapters/responses/request.rs Outdated
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.65772% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/adapters/responses/request.rs 97.31% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 83 untouched benchmarks


Comparing amondnet/codex-r (aad55f0) with main (0c69989)

Open in CodSpeed

… hint

Review of the routing hint added in the previous commit found that
`route.upstream_model` is the client's raw `model` string on prefix-route and
default-provider routes (`routing.rs`, only a trailing `[1m]` stripped), so the
hint interpolated untrusted input into a header value. Upstream codex builds
that hint from its own local config and can interpolate it blind; shunt cannot.

Three consequences, all reachable:

- A control character in `model` made `HeaderValue` rejection surface as a
  deferred reqwest builder error at `.send()`. `retry.rs` classifies that
  non-transient and the Codex OAuth pool charges it to the account as a 30s
  `"transport"` cooldown — and because the error is deterministic it repeated
  for every account, so one malformed client string could cool the entire pool
  before returning 502.
- `model` containing `;` forged a `;tier=` segment the request body never sent,
  falsifying the function's own doc comment.
- `model` is length-unbounded, so the header was too.

`routing_hint` now returns `Option<HeaderValue>` and both forward sites omit the
header rather than failing the turn — the shape upstream uses
(`HeaderValue::from_str(..).ok()` behind an `if let Some`) and the one this repo
already uses for the equally client-derived `x-gateway-model`
(`stamp_gateway_headers`).

The slug guard is a positive allowlist, not a denylist of separators:
`HeaderValue` admits every visible ASCII byte plus TAB, so enumerating
metacharacters against a remote grammar shunt cannot observe is a guard that
erodes — a parser splitting on `,` would read `model=gpt-5,tier=priority` as two
fields, the same forge the `;` rule was added to stop. The charset was verified
against every model-id-shaped literal in config, discovery, and the docs: 120
candidates, 0 rejected. The emptiness clause is load-bearing —
`strip_context_window_hint("[1m]")` returns `""`.

Omission is logged at debug only (client-triggerable, so a higher level is a
log-flood vector) and never logs the model string itself, only its length.

Also in this change: cover `cache_write_tokens` through the nested
`/response/usage` shape real streaming SSE uses, and add the hint to
`m7-codex-websocket.md`'s handshake header list.
@amondnet
amondnet marked this pull request as ready for review August 19, 2026 03:38
@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown

Greptile Summary

The PR synchronizes the mirrored Codex client surface with openai/codex 0.148.0.

  • Updates the pinned Codex client identity.
  • Adds a validated x-codex-routing-hint to ChatGPT OAuth HTTP requests and WebSocket handshakes.
  • Maps upstream cache-write usage into Anthropic cache_creation_input_tokens.
  • Updates tests and documentation across supported locales.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/adapters/responses/request.rs Updates the Codex identity and safely constructs the optional routing-hint header with positive slug validation.
src/adapters/responses/websocket.rs Propagates the routing hint into fresh Codex WebSocket handshakes while preserving credential gating.
src/model/responses.rs Splits cache-write tokens into Anthropic cache-creation usage while preserving the reported input-token total.
tests/responses_translate.rs Adds translation coverage for nested cache-write token usage.

Reviews (2): Last reviewed commit: "docs(memory): bring the routing-hint not..." | Re-trigger Greptile

Comment thread site/src/content/docs/ja/guides/codex.mdx

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 20 files

Architecture diagram
sequenceDiagram
    participant Client as Shunt Inbound Client
    participant Route as Routing Layer
    participant Req as Responses Request Builder
    participant WS as WebSocket Turn Context
    participant Pool as Codex WS Pool
    participant Backend as ChatGPT Backend
    participant SSE as SSE Usage Mapper
    participant Anthropic as Claude Code Client

    Note over Client,Anthropic: Requests with client-controlled model string on Codex OAuth arm

    Client->>Route: Send requests
    Route->>Route: Resolve route.upstream_model (raw client model, [1m] stripped)
    Route-->>Req: Route info

    alt ChatGPT OAuth credential
        Req->>Req: Build x-codex-routing-hint
        alt Model is safe slug (allowlist: 128B, alnum plus hyphen underscore dot colon slash plus)
            Req->>Req: Format model=X plus tier=Y when tier is not default
            Req->>Backend: NEW: Add x-codex-routing-hint header to HTTP request
        else Unusable (control chars, over 128B, empty, semicolon, comma, equals, etc.)
            Req->>Req: Omit header, log debug with model length only
        end
    else api-key/bearer/aws providers
        Req->>Backend: No routing hint header
    end

    Req->>Backend: Send HTTP request with pinned identity 0.148.0

    Req-->>Client: Forward streaming response

    Note over Client,WS: WebSocket path

    Client->>Route: Send request (ws transport)
    Route-->>WS: Route info
    WS->>WS: Compute routing_hint once per turn

    WS->>Pool: begin() with headers
    alt Fresh connection
        Pool->>Backend: WS handshake with x-codex-routing-hint when safe
    else Pooled connection reused
        Pool-->>WS: Reuse existing socket (hint is stale from opening turn)
    end

    Pool-->>Client: Stream events

    Note over Backend,Anthropic: Usage accounting at response.completed

    Backend-->>SSE: response.completed usage (input_tokens, cached_tokens, cache_write_tokens)
    SSE->>SSE: Saturating arithmetic peeling details off total
    alt cache_write_tokens present
        SSE->>Anthropic: NEW: cache_creation_input_tokens equals cache_write_tokens
    else absent
        SSE->>Anthropic: cache_creation_input_tokens stays 0
    end
    SSE->>Anthropic: message_delta usage (three fields sum to prompt total)
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread .claude/agent-memory/review-review-security-reviewer/MEMORY.md Outdated
The index line still read "hint grammar/length not bounded", which contradicts
both the linked note and is_hint_safe_slug — the shipped code enforces a 128-byte
cap plus a positive character allowlist. Restate the entry as the note's own
verdict and keep the real residual (server-side hint grammar unproven).

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 1 file (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread .claude/agent-memory/review-review-security-reviewer/MEMORY.md
Fixing the index entry in b15a06d left the linked note describing the round-2
state: the frontmatter still called the residual a "denylist-of-one grammar
guard" and the body still said a positive slug allowlist "would close it for
good". is_hint_safe_slug ships that allowlist (ASCII alphanumerics plus
- _ . : / +, 128-byte inclusive bound), so both halves now state it as done and
carry the real residual: the server-side parser is unproven.
@sonarqubecloud

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aad55f04a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/adapters/responses/websocket.rs
@amondnet
amondnet merged commit 3fe027f into main Aug 19, 2026
16 checks passed
@amondnet
amondnet deleted the amondnet/codex-r branch August 19, 2026 05:34
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.

1 participant