After connecting Codex CLI to a custom API, most errors fall into six layers: credentials, path, model, rate limiting, context, and the local sandbox. Seeing a red error and immediately switching models or reinstalling the tool often turns a ten-minute problem into half a day. A more effective approach is to hold variables fixed: the same key, Base URL, and model ID, changing one thing at a time. This article breaks errors down by status code and covers log locations, diagnostic commands, and a reusable error-handling example.
Note: OpenAI and Codex are trademarks of OpenAI, Inc. This article is a technical guide only and has no affiliation or partnership with the company. Configuration and messages change between versions; use the official documentation and current version as the reference, and the site's model pricing page for models and prices.
1. Read the status code before changing any configuration
| Symptom | Common meaning | Check first | Retry? |
|---|---|---|---|
401 |
Key missing, wrong, or invalid | env_key variable, key status, balance |
No |
403 |
Key lacks permission or a policy blocked it | Model permissions, account status, team policy | No |
404 |
Wrong path or endpoint does not exist | Base URL, /responses support |
No |
model not found |
Wrong model ID or model not enabled | Model identifier, account permissions | No |
429 |
Rate, concurrency, or quota exceeded | Usage, shared keys, retry settings | Retry after backoff |
400 context_length_exceeded |
Context exceeds the model limit | Input size, tool output, compaction settings | Retry after changing the request |
| Sandbox or approval failure | Local permission boundary blocked it | Sandbox mode, writable directories, approval policy | Retry after adjusting policy |
| Timeout or stream interruption | Proxy, TLS, idle timeout, server overload | Network path, timeout and retry settings | Depends |
Troubleshoot in this order:
- Run
codex doctorin the project directory to check installation, configuration, authentication, and runtime environment. - Inside a session, use
/statusto view the model, approval policy, writable directories, and token usage. - Use
/debug-configto inspect the effective values and confirm you are not editing a file that never gets loaded. - Validate the Base URL, key, and model with a minimal request.
- Only then adjust the model, context, or sandbox policy.
2. 401 and 403: the key never reached the process
A custom provider in Codex names an environment variable through env_key, for example:
[model_providers.zsqh]
name = "ZSQH AI Gateway"
base_url = "https://ai.zhongshangqihang.com/v1"
env_key = "ZSQH_API_KEY"
wire_api = "responses"
A correct configuration does not mean the process can read the key. Common causes: the variable name exported in the shell differs from env_key; the variable was exported in terminal A but Codex was started in terminal B; an application launched from the graphical interface did not inherit the environment variables; the key has leading or trailing spaces, line breaks, or quotes; the key belongs to another platform or environment; or the key was deleted, disabled, or has run out of balance.
Headless environments can pass a key through CODEX_API_KEY, but do not keep several sources at once, or you will struggle to tell which one is actually used. A 403 is more often a permission or policy issue. Read the response body first, then check the gateway console; do not retry repeatedly.
3. 404 and model not found: address, protocol, and model ID
Codex custom providers currently accept only wire_api = "responses", and requests go to /v1/responses. This produces several common 404 cases:
base_urlomits/v1, so requests hit a web route.base_urlalready contains/responses, and the client appends it again, duplicating the path.- The gateway offers only
/v1/chat/completionsand has not implemented the Responses protocol. - The domain is misspelled, or a console address was used as the API address.
Usually base_url should be filled in up to /v1; do not assemble the full endpoint path yourself. When a model does not exist, different gateways may return 404 or 400, and the error code field is commonly model_not_found; rely on the response body to decide. The model ID must exactly match the API identifier on the site's model pricing page, including case, hyphens, and version suffixes.
codex debug models exports the local or built-in catalog, which does not prove which models the gateway actually exposes. Also confirm that model_provider matches the name under [model_providers.xxx]; if they differ, Codex may fall back to the default provider, which looks exactly like "the configuration had no effect."
4. 429: reduce concurrency first, then talk about retries
A 429 means the request exceeded a rate, concurrency, or token quota. Codex reads files and runs commands continuously, so it triggers these limits more easily than ordinary chat. Common scenarios: requests or tokens per minute exceed the limit; several people share one key; a large repository is read file by file in a burst; retries lack backoff and form a storm; long-context requests crowd out concurrency capacity.
The provider configuration in Codex includes request_max_retries (default 4) and stream_max_retries (default 5). They improve stability but amplify request volume during sustained 429 responses, so lower them temporarily while troubleshooting.
Also distinguish rate limiting from unpaid balance: if the error body contains a field such as insufficient_quota, retrying is pointless, and you should check the balance or quota in the console. For genuine rate limiting, look at Retry-After; if the header is absent, use exponential backoff with jitter. Splitting keys by project or by person makes problems easier to locate than one key for the whole group.
5. Context limits: reduce input before switching models
The common error code for an exceeded context is context_length_exceeded, and the message may mention "maximum context length." A Codex request includes not only your prompt but also the system prompt, AGENTS.md, files that were read, tool output, and conversation history. In a long task, the output of every earlier step stays in the context.
Suggested handling order:
- Narrow the working directory so Codex reads only relevant modules.
- Limit the file scope in the prompt to avoid scanning the whole repository.
- Exclude build artifacts, dependency directories, logs, and secret files.
- Use
tool_output_token_limitto cap the tokens of a single tool output. - Start a new session and continue with a short summary of conclusions already reached.
- Only if truly necessary, switch to a model with a longer context.
model_context_window must match the real window; entering a larger number will still be rejected by the server. Setting model_auto_compact_token_limit too high may hit the hard limit first. Use the official documentation and pricing page for actual window sizes.
6. Sandbox and approval failures: these are not API errors
Codex has three sandbox modes: read-only, workspace-write, and danger-full-access. read-only cannot modify files; workspace-write can only modify files inside writable roots and can optionally enable network access; danger-full-access removes file and network boundaries and is suitable only for controlled environments.
Typical symptoms are Operation not permitted, Permission denied, or a prompt asking for approval in the middle of a command. First check the mode and writable directories shown by /status, then confirm whether the project lies outside those directories, whether the command needs network access, and whether the non-interactive codex exec has an approval entry point.
For approval policy, the current version supports on-request, never, and granular. The old approval_policy = "untrusted" is no longer supported, and keeping it may prevent Codex from starting; on-failure is also deprecated. In CI you can explicitly write codex exec --sandbox workspace-write; for read-only tasks, --sandbox read-only --ask-for-approval never works. Do not jump to danger-full-access just to save effort.
7. Timeouts and stream interruptions: proxy, TLS, and idle timeouts
Streaming requests are more sensitive to the network path. Corporate proxies, TLS interception, VPN switches, and unstable egress can all appear as "the request was sent but the first token never arrived," or as a stream that breaks mid-response.
stream_idle_timeout_ms defaults to 300000 milliseconds (five minutes). Reasoning models take longer to produce the first token, so setting it too low causes false failures; if a proxy drops idle connections at 60 seconds, raising the client timeout will not help, and you need to investigate the path as well.
For self-signed CAs or TLS interception, CODEX_CA_CERTIFICATE can point to a PEM certificate bundle, falling back to SSL_CERT_FILE only when unset. Do not disable certificate verification globally. After a timeout, send one minimal non-streaming request to confirm the API works, then reproduce the issue in Codex.
8. Logs and diagnostics: gather evidence before changing configuration
The Codex log directory is controlled by log_dir and defaults to $CODEX_HOME/log. A plaintext codex-tui.log is generated additionally only when log_dir is explicitly set, so enabling it in the user-level configuration is recommended when troubleshooting.
Common diagnostic entry points:
codex doctor: reports installation, configuration, authentication, runtime, and terminal health./status: shows the model, approval policy, writable directories, and token usage within a session./debug-config: shows configuration sources, precedence, and effective values.codex exec --json: emits non-interactive execution as a JSON event stream.RUST_LOG: controls log verbosity, for examplecodex_core=debug,codex_tui=debug;codex execshows only error level by default.
When troubleshooting, align the effective configuration with the actual request: Base URL, /responses path, model name, and approval policy. Many cases of "configuration not taking effect" are caused by the wrong file location or unloaded environment variables.
9. A reusable error-handling example
The example below is for scripts that call the gateway yourself; it does not replace Codex CLI's built-in retries. The principle: never retry authentication or path errors, retry rate limits and 5xx with backoff, and change the request directly for context errors.
import random
import time
import httpx
RETRYABLE = {429, 500, 502, 503, 504}
def call_gateway(payload: dict, key: str, url: str):
for attempt in range(4):
try:
resp = httpx.post(
url,
json=payload,
headers={"Authorization": f"Bearer {key}"},
timeout=httpx.Timeout(180.0, connect=10.0),
)
except httpx.TimeoutException:
if attempt == 3:
raise
time.sleep(min(30, 2 ** attempt) + random.random())
continue
# Authentication, permission, path, and model errors: fix the config, do not retry
if resp.status_code in (401, 403, 404):
raise RuntimeError(f"{resp.status_code}: {resp.text[:300]}")
if resp.status_code == 400:
body = resp.text.lower()
if "context" in body or "maximum context" in body:
raise RuntimeError("Context limit exceeded: reduce input or start a new session")
raise RuntimeError(f"Invalid request: {resp.text[:300]}")
if resp.status_code in RETRYABLE:
if "insufficient_quota" in resp.text:
raise RuntimeError("Quota exhausted: check the balance, do not retry")
retry_after = float(resp.headers.get("Retry-After", 2 ** attempt))
time.sleep(min(30, retry_after) + random.random())
continue
resp.raise_for_status()
return resp.json()
raise RuntimeError("Retry limit reached")
This code does not handle sandbox or approval issues; those belong to the local execution layer. Treating API errors and local permission errors separately makes troubleshooting much faster.
FAQ
The key works in the web console, but Codex returns 401. Why?
Confirm the environment variable name that Codex reads, and export it in the same shell that launches Codex. Applications launched from a graphical interface often cannot see variables set in a shell; reopen the application or use codex doctor to check authentication.
Does a 404 always mean the model was retired?
No. Codex uses the Responses protocol, so a wrong path or a gateway without a /v1/responses endpoint also returns 404. First confirm the address is filled in up to /v1, then verify the model ID.
Should I increase the retry count for 429 errors?
Not recommended. Sustained 429 responses mean the limit is already exceeded, and raising retries only increases request volume. Reduce concurrency, split keys, and check Retry-After to determine whether it is rate limiting or an unpaid balance.
Can Codex use the Chat Completions API?
The current version accepts only wire_api = "responses". Endpoints that offer only Chat Completions need a protocol conversion layer, or you should use another client; do not try to bypass this by installing an old version.
Can I simply turn off sandbox errors?
You can switch to workspace-write and configure writable directories, or use a higher-privilege mode in a controlled environment, but danger-full-access should not be the default. Check /status first to confirm the boundaries.
Next steps
When troubleshooting Codex errors, order matters more than tricks: read the status code and response body first, then use codex doctor and /debug-config to confirm the configuration, and only then change the model or sandbox. If you need a unified key, usage, and model endpoint, sign up for the ZSQH AI Gateway; the model and endpoint scope follow the model pricing page.
Related reading:
