Skip to content

fix(openai): recover message-only capacity failures before output - #5676

Open
Perfecto23 wants to merge 3 commits into
Wei-Shaw:mainfrom
Perfecto23:agent/openai-capacity-failover
Open

fix(openai): recover message-only capacity failures before output#5676
Perfecto23 wants to merge 3 commits into
Wei-Shaw:mainfrom
Perfecto23:agent/openai-capacity-failover

Conversation

@Perfecto23

@Perfecto23 Perfecto23 commented Aug 15, 2026

Copy link
Copy Markdown

Context

OpenAI capacity shedding is not reported through one stable error shape. The same transient overload can arrive as:

  • a non-2xx JSON response;
  • an SSE error event;
  • an SSE response.failed event;
  • a structured server_is_overloaded or slow_down code; or
  • only the message Our servers are currently overloaded. Please try again later.

The gateway previously recognized only part of those shapes. Responses streams also emit lifecycle metadata such as response.created, response.in_progress, empty reasoning items, and empty summary parts before any semantic output. Once those frames were written downstream, the attempt looked committed and the gateway could no longer retry safely. Codex clients then received a capacity error that they can treat as terminal, even though the request was still safe to recover.

This change makes the recovery boundary explicit across HTTP JSON, native SSE, passthrough SSE, and the WebSocket-to-HTTP bridge.

What changed

Classify capacity shedding consistently

  • Recognize structured server_is_overloaded and slow_down codes.
  • Recognize message-only overload responses.
  • Apply the same classification to HTTP errors, SSE error, and response.failed.

Keep pre-output attempts replay-safe

  • Stage lifecycle-only SSE frames until the first semantic output.
  • Stage first-turn WebSocket bridge metadata under the same rule.
  • Discard staged metadata when the attempt fails before semantic output.
  • Exclude keepalive comments from the replay-safety boundary.
  • Bound first-output staging at 8 MiB.

Treat capacity shedding as request-scoped

  • Retry the same account with the existing bounded retry budget.
  • Switch to the next account after that budget is exhausted.
  • Do not disable, cool down, or temporarily unschedule an account for a request-scoped capacity event.

Preserve post-output safety

  • Never replay after semantic output has started.
  • Preserve the upstream error message.
  • Rewrite fatal capacity codes to the retryable server_error code before forwarding them to Codex clients.
  • Keep later WebSocket turns non-replayable.

Add observability

When capacity failover is suppressed because semantic output already started, emit:

gateway.failover_suppressed_after_semantic_output

The event records the transport path, event type, account, platform, and upstream request ID. It does not record request bodies or credentials.

Resulting behavior

Request state Gateway behavior
Capacity failure before semantic output Discard staged metadata, retry the same account, then switch account if needed
Capacity failure after semantic output Do not replay; forward a retryable client error and log suppression
Unknown event shape Fail closed and treat it as output
Rate limit, context limit, content policy, or deterministic request error Preserve the existing policy

Benefits

  • Fewer user-visible failures for transient upstream overloads.
  • No duplicate output from replaying a partially completed response.
  • No account-pool churn caused by treating model capacity as account health.
  • Consistent behavior across HTTP, SSE, passthrough, and WebSocket bridge paths.
  • Clear logs for distinguishing classification failures from intentionally suppressed failover.

Validation

  • go test -tags=unit ./...
  • go test -tags=integration ./...
  • go test ./internal/service ./internal/handler
  • targeted HTTP, native SSE, passthrough SSE, and WebSocket bridge capacity tests
  • go vet ./internal/service ./internal/handler
  • git diff --check

golangci-lint was not run locally because the installed v2.3.0 binary was built with Go 1.24 and cannot load this repository's Go 1.26.6 toolchain. The repository CI should run lint with its supported tool version.

@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA. ✅
Posted by the CLA Assistant Lite bot.

@Perfecto23

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request Aug 15, 2026
@Perfecto23
Perfecto23 marked this pull request as ready for review August 15, 2026 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant