# Codex CLI Can Hide response.failed Behind idle timeout waiting for SSE

# Why Codex CLI Turns response.failed Into idle timeout waiting for SSE

If Codex CLI ends with:

```text
stream disconnected before completion: idle timeout waiting for SSE
```

do not automatically conclude that the upstream server returned nothing. **XBSTACK independently reproduced a different failure shape on Codex CLI 0.153.4 and 0.154.0: the server had already sent a terminal `response.failed` containing a real `server_error`, but because the underlying socket stayed open, Codex kept waiting for EOF until the stream idle timeout fired and replaced the original failure with `idle timeout waiting for SSE`.**

That distinction matters in production. If you keep only the final Codex error, a provider failure or capacity rejection can look like a generic network timeout, sending debugging in the wrong direction.

## Result: 0.154.0 is still reproducible

XBSTACK ran the same local loopback fixture against both versions:

| Codex CLI | Server behavior | Original error preserved | Idle timeout shown |
|---|---|---:|---:|
| `0.153.4` | close immediately after `response.failed` | yes | no |
| `0.153.4` | keep socket open | no | yes |
| `0.154.0` | close immediately after `response.failed` | yes | no |
| `0.154.0` | keep socket open | no | yes |

The open-socket case exited after roughly 1.08 seconds on 0.153.4 and 1.12 seconds on 0.154.0. Those timings are not a benchmark: the fixture deliberately sets `stream_idle_timeout_ms=800` so the behavior appears quickly.

The EOF case preserves the real marker:

```text
stream disconnected before completion: XBSTACK_LOCAL_TERMINAL_FAILURE
```

The hold-open case instead surfaces:

```text
stream disconnected before completion: idle timeout waiting for SSE
```

The SSE event sent by the server is otherwise identical.

## What the fixture proves

The local server returns HTTP 200 with `Content-Type: text/event-stream`:

```text
HTTP/1.1 200 OK
Content-Type: text/event-stream
```

It then sends a terminal failure:

```text
event: response.failed
data: {
  "type": "response.failed",
  "response": {
    "status": "failed",
    "error": {
      "code": "server_error",
      "message": "XBSTACK_LOCAL_TERMINAL_FAILURE"
    }
  }
}
```

Case A closes the connection immediately. Case B leaves it open.

The resulting path is:

```text
HTTP 200
  ↓
SSE response.failed
  ↓
Codex has already parsed a real server_error
  ↓
socket remains open
  ↓
Codex continues waiting for EOF
  ↓
stream idle timeout fires
  ↓
user sees idle timeout waiting for SSE
```

So the problem is not that the server was silent. The problem is that a later transport timeout can replace useful terminal-failure information.

## Why this creates bad debugging decisions

If the real provider error says something like:

```text
server_error: backend capacity unavailable
```

but the final Codex message only says:

```text
idle timeout waiting for SSE
```

it is tempting to increase idle timeouts, add stream retries, blame DNS/TLS/proxies, reinstall Codex, or shrink context. None of those changes necessarily addresses the first failure.

Changing `stream_idle_timeout_ms` is therefore not a root fix. A larger value delays the replacement; a smaller value makes the replacement happen sooner.

## How to tell whether your incident matches this failure shape

Keep at least four layers of evidence:

1. HTTP status;
2. SSE event type;
3. `response.failed.response.error`;
4. final Codex CLI error.

This sequence strongly matches the reproduced behavior:

```text
HTTP 200
response.failed: server_error / <real message>
... socket stays open ...
Codex: idle timeout waiting for SSE
```

If no `response.failed` event arrives at all and the stream simply goes quiet, that is a different, genuine idle-timeout scenario.

## Fully local reproduction

The XBSTACK reproducer requires no model or API account. It uses the Python standard library, a loopback HTTP server, and a fresh temporary `CODEX_HOME` for every case.

```bash
python3 repro.py \
  --codex /Applications/ChatGPT.app/Contents/Resources/codex \
  --output logs/result.json
```

It automatically runs:

```text
Case A: response.failed -> EOF
Case B: response.failed -> keep socket open
```

and records whether the original marker is preserved and whether the idle-timeout string appears.

The EOF case records:

```json
{
  "original_error_preserved": true,
  "idle_timeout_reported": false
}
```

The hold-open case records:

```json
{
  "original_error_preserved": false,
  "idle_timeout_reported": true
}
```

## Why 0.154.0 matters

The upstream issue was originally reported against 0.153.4, but Codex 0.154.0 shipped on September 9. A current troubleshooting page must answer the obvious question: did upgrading already fix it?

XBSTACK therefore ran the exact same fixture against the official OpenAI Codex GitHub stable release `rust-v0.154.0` Apple Silicon binary.

It still reproduced.

As of September 12, 2026, under the controlled conditions above:

> **Upgrading to 0.154.0 is not a verified fix for this failure shape.**

Future stable releases need to be retested rather than inferred from alpha builds or moving `main`.

## The upstream issue includes a proposed patch, not an official released fix

Issue #43140 includes source analysis and a candidate patch whose core idea is to stop the producer after a terminal `response.failed` has already been classified instead of continuing to wait for SSE EOF.

That direction is consistent with the control experiment, but three states must remain separate:

1. the upstream reporter tested a patch locally;
2. XBSTACK independently reproduced the bug behavior;
3. OpenAI merges and releases an official fix.

Only the first two are confirmed here. The issue remained open when checked on September 12, so this article does not tell users that a released version has officially fixed it.

## Production handling now

### Preserve the first terminal failure

For custom providers, Azure OpenAI, gateways, or proxies, keep the raw SSE event whenever possible. The first terminal error is often more diagnostic than a timeout minutes later.

### Correlate provider request IDs

Store request IDs, region/capacity metadata, retry hints, and provider error categories alongside the Codex run or trace ID.

### Do not blame every reconnect on this bug

This article confirms only:

```text
terminal response.failed + socket remains open
```

It does not prove that all of the following come from the SSE consumer:

```text
all server_error responses
all no healthy upstream errors
all TLS failures
all Azure capacity failures
all Codex reconnects
```

### Treat source patches as candidate fixes until released

If you maintain a custom Codex build, the upstream patch and regression tests are worth evaluating. For production teams using official binaries, the safer release test is:

```text
new stable release
  ↓
run the repro
  ↓
EOF case preserves original error
  ↓
open-socket case also preserves original error immediately
  ↓
then mark the issue fixed for your environment
```

## Reliability first, security second

This is primarily a reliability and observability problem, not a reason to turn every Codex failure into a generic AI-security article.

There is still a security/governance implication: if an agent runtime replaces the real execution failure with a secondary timeout, audit trails, alerting, automatic recovery, and incident classification become less trustworthy. Accurate failure preservation is part of runtime control and incident response.

That is why this page belongs in the Codex / AI Tools Lab troubleshooting path while linking into the broader AI Agent Security themes of audit, observability, and runtime control.

## Final takeaway

As of September 12, 2026, XBSTACK independently confirms:

- Codex CLI `0.153.4` is affected;
- Codex CLI `0.154.0` is still affected;
- EOF immediately after `response.failed` preserves the original error;
- keeping the socket open can make Codex wait until idle timeout;
- the final `idle timeout waiting for SSE` can hide the real server failure;
- changing the idle timeout is not a root fix;
- the proposed upstream patch is not yet an official released fix.

If you are debugging Codex SSE reconnects, the first question should be: **did the server already send `response.failed` before the timeout appeared?**

For adjacent streaming failures, compare the [Responses API stream-abort tool-call loss case](/en/ai/openai-responses-api-stream-abort-tool-call-lost/). If you are using Astra or a custom provider, the [GPT-6 Astra API guide](/en/ai/gpt-6-astra-api-guide/) covers the surrounding provider and migration boundaries. Runtime troubleshooting is grouped in [AI Tools Lab](/en/ai/tools-lab/), while authorization, audit, and runtime-control security belongs in [AI Agent Security](/en/ai/security/).

## Primary evidence

- Upstream issue: <https://github.com/openai/codex/issues/43140>
- Codex releases: <https://github.com/openai/codex/releases>
- XBSTACK independent reproduction: https://github.com/xbstack/codex-response-failed-idle-timeout-repro

---

Canonical article: https://www.xbstack.com/en/ai/tools-lab/codex-response-failed-idle-timeout-sse/?utm_source=hashnode&utm_medium=referral&utm_campaign=codex_response_failed_idle_timeout&utm_content=original&ref=hashnode

Related:
- [OpenAI Responses API stream abort and lost tool calls](https://www.xbstack.com/en/ai/openai-responses-api-stream-abort-tool-call-lost/?utm_source=hashnode&utm_medium=referral&utm_campaign=codex_response_failed_idle_timeout&utm_content=related_1&ref=hashnode)
- [MCP Streamable HTTP / SSE pending timeout troubleshooting](https://www.xbstack.com/en/ai/mcp-streamable-http-sse-pending-timeout/?utm_source=hashnode&utm_medium=referral&utm_campaign=codex_response_failed_idle_timeout&utm_content=related_2&ref=hashnode)
- [LangGraph agent error recovery, retry and timeout](https://www.xbstack.com/en/ai/langgraph-agent-error-recovery-retry-timeout/?utm_source=hashnode&utm_medium=referral&utm_campaign=codex_response_failed_idle_timeout&utm_content=related_3&ref=hashnode)
