Skip to content

bug(openai_codex): preserve SSE error details and retry server_is_overloaded failures #234

Closed

Description

@louiseboo

Summary

The OpenAI Codex Responses endpoint can return an HTTP 200 SSE stream containing error and response.failed events with a structured transient-capacity error. Raven currently discards the event details and raises only:

Codex response failed

Because the original server_is_overloaded code/message is lost, Raven's shared error classifier categorizes the result as unknown instead of a retryable server failure. The user receives a generic terminal error without the normal backoff retry behavior.

Redacted evidence

A minimal OAuth-backed request returned:

http_status=200
event_type=error
event_error={
  "type": "service_unavailable_error",
  "code": "server_is_overloaded",
  "message": "Our servers are currently overloaded. Please try again later."
}
event_type=response.failed
event_error={
  "code": "server_is_overloaded",
  "message": "Our servers are currently overloaded. Please try again later."
}

The user-visible result from Raven was:

Error calling Codex: Codex response failed

OAuth was still valid, and a standalone Codex CLI request using the same account succeeded during the same diagnostic window, so this was not a missing credential or expired-subscription condition.

Root cause

On current main, raven/providers/openai_codex_provider.py::_consume_sse() handles both event types with:

elif event_type in {"error", "response.failed"}:
    raise RuntimeError("Codex response failed")

This drops event.error and event.response.error. The provider then returns the generic text as an error response. LLMProvider.classify_error() cannot see overloaded, service unavailable, or the typed service-unavailable error, so it classifies the response as non-retryable unknown.

Steps to reproduce

  1. Configure a valid openai_codex OAuth provider and model.
  2. Call raven doctor --probe or send an ordinary agent turn while the Codex endpoint is returning a capacity error.
  3. Observe the SSE error / response.failed event with code=server_is_overloaded.
  4. Observe that Raven reports only Codex response failed and does not classify it as a retryable server failure.

Expected behavior

  • Preserve a safe, useful error code and message from the SSE event.
  • Classify server_is_overloaded / service_unavailable_error as a transient server failure.
  • Use Raven's existing retry/backoff ladder.
  • Show an actionable final message if retries are exhausted.

Suggested fix scope

  1. Extract error details from event.error and event.response.error in _consume_sse().
  2. Raise or return an error that preserves type, code, and message without exposing credentials or request content.
  3. Ensure the existing classifier sees server_is_overloaded or service unavailable and marks the failure retryable.
  4. Add regression tests for:
    • error with service_unavailable_error;
    • response.failed with server_is_overloaded;
    • backoff retry followed by success;
    • retry exhaustion returning the real, safe message;
    • unrelated non-retryable SSE failures.

Environment

  • macOS arm64
  • Raven CLI v0.1.3 locally
  • Confirmed that the same generic SSE error handling remains on current main

Privacy

This report contains no OAuth tokens, account identifiers, usernames, private paths, images, chat content, or business files.

Activity

  1. AmirF194 commented on Jul 31, 2026

    @AmirF194
    Contributor

    Reproduced at HEAD 16bdca7d in a clean python:3.12-slim container (httpx + loguru installed directly, no OAuth or network needed): built an httpx.Response carrying the two SSE events from the report's redacted evidence (type: error and type: response.failed, both code: server_is_overloaded) and ran it through _consume_sse() and OpenAICodexProvider.classify_error() directly.

    raised: RuntimeError('Codex response failed')
    classify_error(exc) -> ErrorClassification(category='unknown', retryable=False, should_fallback=False, should_compress=False)
    

    Matches the report exactly. _consume_sse() (openai_codex_provider.py:325-326) drops event["error"] and event["response"]["error"] and raises a fixed string, so classify_error() (base.py:298-414) has nothing to match against its overloaded / service unavailable substrings and falls through to the unknown bucket at :414, which is non-retryable by default.

    git log -G 'Codex response failed' --follow on this file returns only the initial commit, so this looks unimplemented rather than a deliberate choice.

  2. fancyboi999 commented on Aug 4, 2026

    @fancyboi999

    Hi, I'd like to work on this issue.

    I reproduced the current behavior on the latest main without OAuth or a network call: a synthetic HTTP 200 SSE response.failed event containing service_unavailable_error and server_is_overloaded is reduced to RuntimeError("Codex response failed"), then classified as unknown with retry and fallback disabled.

    My planned scope is:

    • extract bounded, safe type, code, and message fields from both error and response.failed events without serializing the full event;
    • preserve those details through the existing error classifier so the normal retry/fallback ladder applies;
    • add deterministic coverage for both SSE shapes, malformed or unrelated failures, retry-then-success, and retry exhaustion;
    • run the focused provider tests and the repository's full make ci gate.

    Please let me know if there is a preferred error representation or another implementation already in progress.

  3. added a commit that references this issue on Aug 10, 2026
    56f7095
  4. added a commit that references this issue on Sep 14, 2026
    5719cbe
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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions