Pre-flight checklist
codex-lb version
1.24.0-beta.4
Deployment method
Local macOS LaunchAgent, bound to 127.0.0.1:2455, using the SQLite store.
Client used against codex-lb
- Codex Desktop / Codex CLI
0.151.0-alpha.7.1
- Responses WebSocket transport
ChatGPT account plan(s) involved
Plus accounts. A successful native Fast control using the same account class is documented in #1454.
Model(s) involved
gpt-5.6-sol (the reuse defect is model-independent)
Summary
When a Codex task starts in Standard mode and Fast mode is enabled while the downstream Responses WebSocket remains open, subsequent response.create frames correctly contain service_tier: "priority", but codex-lb continues using the already-open upstream WebSocket created under the old routing contract.
The request succeeds, but upstream returns response.completed.response.service_tier = "default". This is a silent downgrade, not a request failure.
This is narrower than #1454:
What happened?
In a controlled long-lived task:
| Interval |
Requested tier |
Completed actual tier |
| Before Fast was enabled |
omitted |
default |
| After Fast was enabled |
priority in 138/138 requests |
default in 138/138 requests |
The database and completed-event telemetry agree: the Fast intent reached codex-lb, but no request in that interval was granted priority.
Across the current local history there are 9,969 requests with requested_service_tier=priority and zero with actual_service_tier=priority. That aggregate also contains the broader fresh-connection problem from #1454; the 138-request interval above is the bounded reproduction for this report.
Code-path diagnosis
The relevant implementation is app/modules/proxy/_service/websocket/mixin.py in 1.24.0-beta.4:
- Downstream WebSocket handshake headers are captured once when the connection starts (around line 1356).
- The implementation has explicit upstream-reuse guards for model-source compatibility (around line 1772) and response ownership (around line 2184).
- The upstream WebSocket is opened only when
upstream is None (around line 2356).
- Later
response.create frames can carry a different service_tier, but no equivalent reuse guard compares the effective tier, routing hint, or attestation/installation identity against the active upstream connection.
This produces an internally inconsistent request:
response.create body: service_tier=priority
active upstream WS: handshake created under the previous/default routing contract
reuse decision: connection is open, so it is retained
completed result: service_tier=default
The native Codex client sends x-codex-routing-hint: model=<model>;tier=priority on the upstream WebSocket handshake. OpenAI PR openai/codex#37345 describes that header as carrying the model and tier to the Codex backend, including for WebSocket handshakes and prewarm.
Therefore the effective service tier is part of the connection-level routing contract, not merely a per-frame body field.
Interaction with PR #1995
PR #1995 adds native Codex traffic parity, but its current patch classifies x-codex-routing-hint as hop-local and removes it from both HTTP and WebSocket upstream traffic (app/core/clients/proxy.py, IGNORE_INBOUND_HEADERS). Its unit test explicitly expects that header to be absent upstream.
That conflicts with openai/codex#37345. Unless codex-lb consumes the incoming hint and recreates an equivalent server-facing routing decision, stripping it can preserve or worsen the Fast downgrade.
Also, the fast-canary in #1995 means a quick canary; it does not assert that a Fast request receives response.completed.response.service_tier=priority. The B/C comparator can pass when both proxied captures return default, while the native A capture returning priority is informational.
Steps to reproduce
- Run codex-lb
1.24.0-beta.4 with Responses WebSocket transport enabled.
- Point Codex Desktop or CLI at
http://127.0.0.1:2455/backend-api/codex.
- Start a task in Standard mode and submit one turn so the downstream and upstream WebSockets are established.
- Enable Fast mode without restarting the task or connection.
- Submit one or more turns.
- Inspect the outbound
response.create and the completed event/request log.
- Observe
requested_service_tier=priority, no new upstream handshake attributable to the tier change, and actual_service_tier=default.
Expected behavior
Changing the effective service tier on a live downstream WebSocket should not reuse an upstream connection carrying a stale routing contract.
At minimum:
- the active upstream connection contract should include endpoint, model, effective service tier, routing hint, selected account, and installation identity;
- changing any routing-relevant field should trigger a fresh upstream handshake;
- the new handshake should preserve or reconstruct the correct
x-codex-routing-hint;
- tests should cover Standard → Fast and Fast → Standard on the same downstream connection;
- telemetry should warn when
requested_service_tier=priority completes as a different actual tier.
Relevant sanitized evidence
bounded task interval:
requested priority: 138
actual priority: 0
actual default: 138
local aggregate:
requested priority: 9,969
actual priority: 0
No tokens, full account IDs, installation IDs, email addresses, request IDs, or conversation contents are included here. I can provide a targeted sanitized connection timeline or run a maintainer-provided probe if needed.
Related
Pre-flight checklist
codex-lb version
1.24.0-beta.4Deployment method
Local macOS LaunchAgent, bound to
127.0.0.1:2455, using the SQLite store.Client used against codex-lb
0.151.0-alpha.7.1ChatGPT account plan(s) involved
Plus accounts. A successful native Fast control using the same account class is documented in #1454.
Model(s) involved
gpt-5.6-sol(the reuse defect is model-independent)Summary
When a Codex task starts in Standard mode and Fast mode is enabled while the downstream Responses WebSocket remains open, subsequent
response.createframes correctly containservice_tier: "priority", but codex-lb continues using the already-open upstream WebSocket created under the old routing contract.The request succeeds, but upstream returns
response.completed.response.service_tier = "default". This is a silent downgrade, not a request failure.This is narrower than #1454:
What happened?
In a controlled long-lived task:
defaultpriorityin 138/138 requestsdefaultin 138/138 requestsThe database and completed-event telemetry agree: the Fast intent reached codex-lb, but no request in that interval was granted
priority.Across the current local history there are 9,969 requests with
requested_service_tier=priorityand zero withactual_service_tier=priority. That aggregate also contains the broader fresh-connection problem from #1454; the 138-request interval above is the bounded reproduction for this report.Code-path diagnosis
The relevant implementation is
app/modules/proxy/_service/websocket/mixin.pyin1.24.0-beta.4:upstream is None(around line 2356).response.createframes can carry a differentservice_tier, but no equivalent reuse guard compares the effective tier, routing hint, or attestation/installation identity against the active upstream connection.This produces an internally inconsistent request:
The native Codex client sends
x-codex-routing-hint: model=<model>;tier=priorityon the upstream WebSocket handshake. OpenAI PR openai/codex#37345 describes that header as carrying the model and tier to the Codex backend, including for WebSocket handshakes and prewarm.Therefore the effective service tier is part of the connection-level routing contract, not merely a per-frame body field.
Interaction with PR #1995
PR #1995 adds native Codex traffic parity, but its current patch classifies
x-codex-routing-hintas hop-local and removes it from both HTTP and WebSocket upstream traffic (app/core/clients/proxy.py,IGNORE_INBOUND_HEADERS). Its unit test explicitly expects that header to be absent upstream.That conflicts with openai/codex#37345. Unless codex-lb consumes the incoming hint and recreates an equivalent server-facing routing decision, stripping it can preserve or worsen the Fast downgrade.
Also, the
fast-canaryin #1995 means a quick canary; it does not assert that a Fast request receivesresponse.completed.response.service_tier=priority. The B/C comparator can pass when both proxied captures returndefault, while the native A capture returningpriorityis informational.Steps to reproduce
1.24.0-beta.4with Responses WebSocket transport enabled.http://127.0.0.1:2455/backend-api/codex.response.createand the completed event/request log.requested_service_tier=priority, no new upstream handshake attributable to the tier change, andactual_service_tier=default.Expected behavior
Changing the effective service tier on a live downstream WebSocket should not reuse an upstream connection carrying a stale routing contract.
At minimum:
x-codex-routing-hint;requested_service_tier=prioritycompletes as a different actual tier.Relevant sanitized evidence
No tokens, full account IDs, installation IDs, email addresses, request IDs, or conversation contents are included here. I can provide a targeted sanitized connection timeline or run a maintainer-provided probe if needed.
Related
x-codex-routing-hint