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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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