Skip to content

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:

text
stream disconnected before completion: stream closed before response.completed
stream disconnected before completion: idle timeout waiting for SSE

The 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.

text
unexpected status 401 Unauthorized: <response body>, url: https://byesu.com/v1/responses
unexpected status 404 Not Found: <response body>, url: https://byesu.com/responses

The 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.

text
`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/7782

The config failed to load, so no request was sent.

text
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 appends responses. https://byesu.com/v1 becomes https://byesu.com/v1/responses; https://byesu.com becomes https://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 "use wire_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 by codex login or in auth.json) and, per the authentication docs, ignores env_key. Without it, the key comes from the variable named in env_key.
  • TOML table scope. Every key after a [model_providers.x] header belongs to that table until the next header, so a model = ... line placed below it never reaches the top level.

Fix 1: Make base_url End at the Version Root ​

base_url you wroteURL Codex callsResult
https://byesu.comhttps://byesu.com/responses404
https://byesu.com/v1/responseshttps://byesu.com/v1/responses/responses404
https://byesu.com/v1/chat/completions.../v1/chat/completions/responses404
https://byesu.com/v1 or https://byesu.com/v1/https://byesu.com/v1/responsesCorrect

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:

bash
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) ​

toml
[model_providers.byesu]
name = "byesu"
base_url = "https://byesu.com/v1"
wire_api = "responses"   # the only supported value; also the default

If 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:

bash
printenv BYESU_API_KEY | codex login --with-api-key

or in ~/.codex/auth.json:

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:

toml
[model_providers.byesu]
name = "byesu"
base_url = "https://byesu.com/v1"
wire_api = "responses"
env_key = "BYESU_API_KEY"
bash
export BYESU_API_KEY="sk-your-token"   # add to ~/.zshrc or ~/.bashrc to persist
codex

Missing 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:

toml
# ~/.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. model and model_provider must 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.toml is not allowed to set model_provider, model_providers or openai_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-level openai_base_url instead of a [model_providers.openai] table.
  • Verify. Run codex doctor, start codex, then type /status to 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:

toml
[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. Raise stream_idle_timeout_ms, or lower model_reasoning_effort for 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" with wire_api = "responses". byesu serves the Responses API at /v1/responses, so the URL Codex builds is the right one. Model IDs such as gpt-6.1-sol or gpt-6-sol go in the top-level model line, copied from the console model list.
  • Key handling as in our guide: requires_openai_auth = true with your sk- token stored under OPENAI_API_KEY in ~/.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.

Got a problem? Contact support or join our group.