Skip to content

[Enhancement]: Mid-stream OpenAI socket resets can break Codex account affinity #919

Description

@luvs01

Client or integration

Codex App

Area

Proxy and routing

Summary

A native OpenAI Responses stream can return HTTP 200 and partial SSE data, then fail while OpenCodex reads the upstream body with a socket reset such as ECONNRESET.

Codex correctly treats the resulting stream disconnected before completion: Transport error: network error: error decoding response body as retryable and replays the logical request. OpenCodex should not replay a partially delivered stream because that could duplicate tool calls or other side effects.

The routing side effect is incorrect: the passthrough inspection path converts a mid-stream body-read failure into synthetic status 502 and records it through recordCodexUpstreamOutcome. Repeated client retries or concurrent streams can then reach upstreamFailoverThreshold, soft-avoid a healthy credential, clear thread affinity, and promote another account even though no account-specific HTTP rejection occurred.

Expected behavior:

  • Keep the synthetic mid-stream 502 in request diagnostics.
  • Do not apply that synthetic transport terminal to account failure streak, cooldown, affinity, or active-account selection.
  • Release only a quota-probe lease owned by the interrupted request.
  • Continue recording actual HTTP 429/402/5xx and semantic SSE response.failed outcomes.
  • Do not replay the partially delivered request inside OpenCodex; leave bounded retry to Codex.
  • Preserve the selected account for the Codex retry unless an actual account-specific rejection occurs.
  • Record enough correlation-safe metadata to distinguish upstream body-read failure, downstream pipe failure, client cancellation, and terminal delivery.

Relevant source:

  • src/server/relay.ts: passthrough inspection maps body-read failure to a synthetic 502 terminal.
  • src/server/responses/core.ts: native passthrough terminal recording forwards that synthetic status to account routing.
  • src/codex/routing.ts: repeated transient outcomes can clear affinity and promote another account.
  • structure/04_transports-and-sidecars.md: mid-stream SSE failures are intentionally not replayed by the proxy.

Required regression coverage:

  • HTTP 200 SSE emits a partial delta, then its body throws ECONNRESET three times for the same thread.
  • Each request remains observable as a synthetic mid-stream failure, but account health, affinity, and active selection remain unchanged.
  • A held scoped or account-wide probe lease is released exactly once.
  • An actual SSE response.failed with status 502 still records an account failure.
  • A client cancellation remains distinct and does not become an upstream failure.
  • No extra upstream request is sent by OpenCodex after partial delivery.

Related: #914 handles DNS/reachability failures before response headers; this issue handles failures after HTTP 200 and partial body delivery. #913 covers compact failover only after an actual upstream 429/402. The same client-visible error is tracked upstream in openai/codex#29087.

Reproduction

  1. Configure OpenAI Codex-login pool routing and bind a thread to account A.
  2. Return HTTP 200 with text/event-stream from the OpenAI upstream.
  3. Deliver at least one valid SSE delta, then make the upstream body reader throw an ECONNRESET-shaped error before response.completed.
  4. Let Codex retry the request several times or reproduce the same failure concurrently.

Current: OpenCodex records synthetic 502 account failures. At the configured threshold it can clear A's affinity or promote another account without an actual HTTP rejection.

Expected: Codex can retry, but OpenCodex keeps account routing state unchanged and never replays the partial stream itself.

Version

2.10.0; the relevant passthrough terminal and routing behavior remained on dev at c72acb3e368d2613c41d6d2b15a771895843f03f when checked on 2026-08-03.

Operating system

Windows 11 Pro 10.0.26200. The account-outcome classification issue is platform-independent; the observed resets occurred in the Windows native passthrough path.

Provider and model

OpenAI (Codex login / forward account pool); native Codex Responses streaming.

Logs or error output

stream disconnected before completion: Transport error: network error: error decoding response body
TypeError: The socket connection was closed unexpectedly
code: ECONNRESET

Screenshots and supporting files

None. Exact request IDs, thread IDs, account identifiers, quota values, and timestamps are intentionally omitted.

Redacted configuration

{
  "providers": {
    "openai": {
      "adapter": "openai-responses",
      "authMode": "forward",
      "codexAccountMode": "pool"
    }
  },
  "upstreamFailoverThreshold": 3
}

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingenhancementNew feature or requestproxyHTTP proxy, routing, reverse-proxy / management authstreamingSSE, WebSocket, terminal stream framestoolstool_calls, MCP, web-search / sidecar tools

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions