Codex CLI Custom Provider Errors: stream disconnected, 401, 404 and wire_api
You defined a custom model_provider in ~/.codex/config.toml and Codex fails on the first prompt, or it starts and then drops mid-answer. The error text Codex prints narrows the cause down quickly, and each fix below is a one- or two-line config change.
Quick answer
Codex sends every request to base_url + /responses and speaks only the Responses API. So base_url must end at the version root (https://byesu.com/v1), and wire_api must be "responses"; "chat" has been removed. unexpected status 401 means the gateway didn't get a key it accepts (check requires_openai_auth vs env_key). unexpected status 404 usually means the path is wrong. stream disconnected before completion means the SSE stream ended before response.completed, usually because of a proxy or timeout rather than your config.
Symptoms and What They Mean
These strings come straight from the Codex source (protocol/src/error.rs, model-provider-info/src/lib.rs), so you can match them exactly:
stream disconnected before completion: stream closed before response.completed
stream disconnected before completion: idle timeout waiting for SSEThe stream opened but ended without the final response.completed event, or went silent for longer than the idle timeout. Codex has already retried before it shows this.
unexpected status 401 Unauthorized: <response body>, url: https://byesu.com/v1/responses
unexpected status 404 Not Found: <response body>, url: https://byesu.com/responsesThe gateway answered with a non-success status. The body is the gateway's own text, and the url: suffix shows the exact URL Codex called. Check it first.
`wire_api = "chat"` is no longer supported.
How to fix: set `wire_api = "responses"` in your provider config.
More info: https://github.com/openai/codex/discussions/7782The config failed to load, so no request was sent.
Missing environment variable: `BYESU_API_KEY`.The provider has env_key = "BYESU_API_KEY", but that variable is unset or empty in the process that started Codex.
Why It Happens
- Codex builds the request URL itself. It trims slashes from
base_url, then appendsresponses.https://byesu.com/v1becomeshttps://byesu.com/v1/responses;https://byesu.combecomeshttps://byesu.com/responses, which doesn't exist. - Only one wire protocol remains. The config reference says
responses"is the only supported value, and it is the default when omitted." Older guides that say "usewire_api = "chat"for OpenAI-compatible providers" are out of date. - There are two credential paths, and they don't combine. With
requires_openai_auth = true, Codex uses OpenAI-style auth (the key saved bycodex loginor inauth.json) and, per the authentication docs, ignoresenv_key. Without it, the key comes from the variable named inenv_key. - TOML table scope. Every key after a
[model_providers.x]header belongs to that table until the next header, so amodel = ...line placed below it never reaches the top level.
Fix 1: Make base_url End at the Version Root
base_url you wrote | URL Codex calls | Result |
|---|---|---|
https://byesu.com | https://byesu.com/responses | 404 |
https://byesu.com/v1/responses | https://byesu.com/v1/responses/responses | 404 |
https://byesu.com/v1/chat/completions | .../v1/chat/completions/responses | 404 |
https://byesu.com/v1 or https://byesu.com/v1/ | https://byesu.com/v1/responses | Correct |
A trailing slash is harmless, since Codex trims it. Test the endpoint outside Codex so you know whether the problem is the URL or the client:
curl -sS -w '\n%{http_code}\n' https://byesu.com/v1/responses \
-H "Authorization: Bearer sk-your-token" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-6.1-sol", "input": "Reply with: ok"}'If this returns 200 and Codex still 404s, compare the url: in the Codex error with the URL above. One more 404 cause: some gateways answer an unknown model with 404 as well, so read the response body before you change the URL.
Fix 2: Set wire_api = "responses" (or Drop the Line)
[model_providers.byesu]
name = "byesu"
base_url = "https://byesu.com/v1"
wire_api = "responses" # the only supported value; also the defaultIf a provider only offers /v1/chat/completions, current Codex can't talk to it directly. Switching wire_api won't help, because there is nothing left to switch to. Use an endpoint that implements the Responses API, including its streaming events, because Codex waits for response.completed to finish each turn.
Fix 3: Pick One Credential Path and Make the Key Reach Codex
Option A: requires_openai_auth = true (what our Codex setup guide uses). Codex sends the key saved by its own login. Store your gateway key either with the official command:
printenv BYESU_API_KEY | codex login --with-api-keyor in ~/.codex/auth.json:
{ "OPENAI_API_KEY": "sk-your-token" }The field name stays OPENAI_API_KEY even when the key belongs to a gateway. In this mode env_key is ignored, so exporting a variable won't change anything. Codex can also store credentials in your OS keychain instead of auth.json, depending on cli_auth_credentials_store (file, keyring, auto, ephemeral). If editing auth.json seems to do nothing, check that setting or use codex login --with-api-key.
Option B: env_key. Codex reads the key from a named environment variable and sends it as a bearer token:
[model_providers.byesu]
name = "byesu"
base_url = "https://byesu.com/v1"
wire_api = "responses"
env_key = "BYESU_API_KEY"export BYESU_API_KEY="sk-your-token" # add to ~/.zshrc or ~/.bashrc to persist
codexMissing environment variable means Codex can't see the variable. It may be unset, set to only whitespace (which counts as missing), or exported in a different shell. OpenAI's docs also warn that a variable set in a terminal may not reach an app launched from the desktop.
Still getting 401? You now know which header carries the key, so check the key itself. Make sure it has no stray newline, that it hasn't been deleted or disabled in the gateway console, and that you don't have both requires_openai_auth = true and an env_key you expected to be used. Command-backed [model_providers.x.auth] can't be combined with env_key, experimental_bearer_token or requires_openai_auth. For 401 causes in general, see Claude API 401 Authentication Error.
Fix 4: Check That Your Provider Is Actually Active
If the error shows api.openai.com in its url:, or /status shows the wrong provider, your provider block isn't being used:
# ~/.codex/config.toml: top-level keys FIRST
model = "gpt-6.1-sol"
model_provider = "byesu"
[model_providers.byesu]
name = "byesu"
base_url = "https://byesu.com/v1"
wire_api = "responses"
requires_openai_auth = true- Order matters.
modelandmodel_providermust come before the first[table]header, or TOML assigns them to that table. OpenAI's gateway guide calls this out explicitly. - Project config can't switch providers. A repo's
.codex/config.tomlis not allowed to setmodel_provider,model_providersoropenai_base_url. Codex ignores them there and prints a startup warning. Keep them in~/.codex/config.toml. - Reserved IDs. You can't redefine built-in IDs such as
openai. To redirect the built-in OpenAI provider, use top-levelopenai_base_urlinstead of a[model_providers.openai]table. - Verify. Run
codex doctor, startcodex, then type/statusto see the active model and provider.
Fix 5: Tame stream disconnected before completion
Codex retries before it shows this error, and you can tune each provider separately. The defaults below come from the config reference:
[model_providers.byesu]
# ...base_url, wire_api, auth as above...
request_max_retries = 4 # HTTP request retries (default 4)
stream_max_retries = 10 # reconnects after a dropped stream (default 5)
stream_idle_timeout_ms = 600000 # silence allowed on the stream (default 300000)Raising these hides short network blips. It won't fix a stream that breaks the same way every time. Read the reason after the colon:
idle timeout waiting for SSE. Nothing arrived for the whole idle window. This is common on high reasoning effort, where the first visible token can take minutes, or when a proxy buffers the stream. Raisestream_idle_timeout_ms, or lowermodel_reasoning_effortfor that task.stream closed before response.completed. The connection closed early. If it happens on every request, something in between is ending or rewriting the stream: a corporate proxy, an nginx hop with buffering on, or an endpoint that sends Chat-Completions-style chunks instead of Responses events. If it happens only now and then, it is usually network trouble, and the built-in retries handle most of it.
The proxy-side fixes (buffering, idle timeouts, CDNs) are covered in detail in Streaming Connection Dropped.
If You're Using byesu
byesu is an AI API gateway with OpenAI-compatible and Anthropic-native endpoints: one key for GPT, Claude, Grok and Gemini, billed pay-as-you-go with no subscription. For Codex:
base_url = "https://byesu.com/v1"withwire_api = "responses". byesu serves the Responses API at/v1/responses, so the URL Codex builds is the right one. Model IDs such asgpt-6.1-solorgpt-6-solgo in the top-levelmodelline, copied from the console model list.- Key handling as in our guide:
requires_openai_auth = truewith yoursk-token stored underOPENAI_API_KEYin~/.codex/auth.json, or Option B above if you prefer an environment variable.
To get started, create a token in the console and follow the Quick Start. The full walkthrough, including a one-click setup script, is in Connecting Codex CLI.
FAQ
What does stream disconnected before completion mean in Codex CLI?
The streaming response ended before Codex received the response.completed event, or no data arrived within the idle timeout (300000 ms by default). Codex retries a dropped stream up to stream_max_retries times (default 5) before showing the error. Persistent failures usually mean a proxy is buffering or cutting the SSE stream, or the endpoint is not a full Responses API implementation.
Can I still use wire_api = "chat" in Codex?
No. Current Codex releases only support wire_api = "responses", which is also the default when the key is omitted. A config with wire_api = "chat" fails to load with the message that chat is no longer supported. Your provider must expose the OpenAI Responses API at base_url/responses.
What should base_url be for a Codex custom provider?
The API root including its version path, for example https://byesu.com/v1. Codex appends /responses itself, so a base_url without /v1 or one that already ends in /responses produces a wrong path and a 404.
Why does Codex return unexpected status 401 with a custom provider?
The gateway did not receive a key it accepts. Check which auth mode the provider uses: with requires_openai_auth = true Codex sends the key stored by codex login or auth.json and ignores env_key; with env_key it reads the named environment variable, which must be exported in the process that launches Codex.
Why is my model_provider setting ignored?
Two common reasons. Top-level keys such as model and model_provider placed after a [model_providers.x] header belong to that table, not the top level. And a project-local .codex/config.toml cannot set model_provider or model_providers; Codex ignores them there with a startup warning. Put them at the top of ~/.codex/config.toml.
