Authorization header lost when falling back from WebSocket to HTTPS with custom openai_base_url
Open 💬 6 comments Opened Mar 23, 2026 by fan454705762-sketch
💡 Likely answer: A maintainer (github-actions[bot], contributor)
responded on this thread — see the highlighted reply below.
What version of Codex CLI is running?
0.116.0
What subscription do you have?
When using a custom openai_base_url pointing to an OpenAI-compatible gateway, Codex CLI fails to send the Authorization: Bearer header during the HTTPS fallback after WebSocket connection fails.
Which model were you using?
_No response_
What platform is your computer?
_No response_
What terminal emulator and version are you using (if applicable)?
_No response_
What issue are you seeing?
Bug Description
When using a custom openai_base_url pointing to an OpenAI-compatible gateway, Codex CLI fails to send the Authorization: Bearer header during the HTTPS fallback after WebSocket connection fails.
Environment
- codex-cli: 0.116.0
- OS: Linux (Docker container, node:22-bookworm-slim)
- Gateway: New API (OpenAI-compatible gateway supporting
/v1/responsesendpoint)
What steps can reproduce the bug?
- Set up an OpenAI-compatible gateway that supports the Responses API (
/v1/responses) but not WebSocket - Run:
OPENAI_API_KEY="sk-valid-key" codex exec --json --skip-git-repo-check \
-c 'openai_base_url="https://my-gateway.example.com/v1"' \
--model gpt-5.4 --dangerously-bypass-approvals-and-sandbox - <<< "say hello"
### What is the expected behavior?
Expected Behavior
After WebSocket fallback, Codex should send Authorization: Bearer <OPENAI_API_KEY> in the HTTPS request to /v1/responses.
Actual Behavior
WebSocket connection to wss://my-gateway.example.com/v1/responses fails (expected, gateway doesn't support WebSocket)
Falls back to HTTPS at https://my-gateway.example.com/v1/responses
Gets 401 Unauthorized because no Authorization header is sent
### Additional information
Verification
The same gateway works perfectly with curl:
curl -s \
-H "Authorization: Bearer sk-valid-key" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-5.4","input":"say hi","stream":true}' \
https://my-gateway.example.com/v1/responses
# Returns correct SSE stream with response.completed event
Also, the same provider mode has the same issue:
OPENAI_API_KEY="sk-valid-key" codex exec --json --skip-git-repo-check \
-c 'model_provider="mygateway"' \
-c 'model_providers.mygateway.base_url="https://my-gateway.example.com/v1"' \
-c 'model_providers.mygateway.wire_api="responses"' \
-c 'model_providers.mygateway.requires_openai_auth=true' \
--model gpt-5.4 --dangerously-bypass-approvals-and-sandbox - <<< "say hello"
# Also gets 401 on HTTPS fallback
This suggests the auth header is being dropped during the WebSocket → HTTPS fallback path when using any custom base URL.
6 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Hi, I've been looking into this and traced the root cause.
Proposed approach: Trace the WebSocket→HTTPS fallback code path in codex-cli. When WS connection fails with a custom openai_base_url, the HTTPS fallback constructs a new request but drops the Authorization Bearer header. Fix by ensuring the auth headers (constructed from OPENAI_API_KEY or provider config) are forwarded to the HTTPS request in the fallback path. Add a test verifying auth headers survive the fallback. Comment on issue first per comment-before-pr rule.
I'll open a draft PR shortly. Happy to adjust based on your preference.
Codex doesn't support
OPENAI_API_KEYby default. You can use the env var by defining the provider explicitly:Thanks @pakrym-oai for the clarification. Defining the provider explicitly with env_key does resolve the auth issue.
However, I'd like to flag a separate efficiency problem in the same scenario: when openai_base_url points to a gateway that doesn't support WebSocket, the WS handshake returns 404 Not Found. Currently in stream_responses_websocket() 1, only 426 UPGRADE_REQUIRED triggers an immediate FallbackToHttp. A 404 falls through to the generic error path, gets classified as retryable, and exhausts the full 5-retry budget before finally falling back to HTTP.
404 is semantically equivalent to 426 here — both are deterministic server responses indicating "this WS endpoint doesn't exist." Retrying won't change the outcome. Network errors (timeouts, connection resets, 5xx) would still be retried normally.
Proposed fix: add || status == StatusCode::NOT_FOUND to the existing 426 match arm. One-line change, zero risk to existing behavior.
Should I open a separate issue for this, or would you prefer I file a PR if the approach looks right?
I picked up the follow-up discussed here: after
env_key/explicit-provider config resolves the auth misunderstanding, the remaining real bug is that a deterministic WebSocket handshake404 Not Foundstill burns the full retry budget instead of falling back to HTTP immediately.I pushed a minimal fix here:
fix/websocket-404-http-fallbackace4a7b22e1de1259987cd48b0a719202d92dce2What changed:
404 Not Foundthe same way as426 Upgrade Requiredinstream_responses_websocket(), i.e. immediateFallbackToHttp404503handshake failures thereLocal validation:
just fix -p codex-corejust fmtgit diff --checkjust argument-comment-lintis blocked in this environment becausebazelis not installedcargo test -p codex-core websocket_fallback_passed earlier for the behavioral change; after the final clippy-only pattern-match cleanup, rerunning it in this machine failed during test-binary linking withld: write() failed, errno=28because the workspace only had about517 MiBfreeSince upstream PRs are invitation-only here, I am not opening a direct PR. If this direction looks right, a maintainer can invite a PR from the branch above or cherry-pick
ace4a7b.This looks like a real fallback-path issue rather than a normal key/config problem.
From your reproduction, curl works because it sends:
Authorization: Bearer <key>
But Codex seems to drop the Authorization header only after the WebSocket request fails and falls back to HTTPS.
A useful way to isolate it may be:
If the header is present in the first path but missing in the fallback path, then it is probably not a gateway issue. It is likely that the custom provider auth config is not being reused during fallback.
This is especially important for OpenAI-compatible gateways that support /v1/responses but do not support WebSocket.