openai/codex · error · LunaSamplerError

Luna Responses WebSocket failed: {0}

Error message

Luna Responses WebSocket failed: {0}

What it means

Wraps an ApiError raised while opening or streaming the Luna Responses WebSocket: connect failures, stream_request errors, and event-receive errors. The sampler already retries retryable variants (Stream, Retryable, ServerOverloaded, transport timeout/connection, 5xx/429, 401 with auth recovery) up to 2 times, so this escaping means non-retryable or retries exhausted. The nested ApiError discriminates HTTP status vs transport vs stream.

Source

Thrown at codex-rs/ext/guardian-v2/src/async_scorer/sampler.rs:113

    pub parent_compaction: Option<ResponseItem>,
    /// Current parent model's encrypted-compaction compatibility hash.
    pub parent_compaction_hash: Option<String>,
    /// Strict JSON schema constraining the model response.
    pub output_schema: Value,
    /// Reasoning budget explicitly selected for this request.
    pub reasoning_effort: ReasoningEffort,
    /// Owning turn identifier used for request attribution.
    pub turn_id: String,
}

/// Failures returned while connecting or sampling the Luna model.
#[derive(Debug, Error)]
pub enum LunaSamplerError {
    /// The thread's provider or scoped credentials could not be resolved.
    #[error("could not resolve the Luna model provider: {0}")]
    Provider(#[source] CodexErr),
    /// The Responses WebSocket could not be opened or streamed.
    #[error("Luna Responses WebSocket failed: {0}")]
    Api(#[source] ApiError),
    /// The provider's WebSocket connect deadline elapsed.
    #[error("Luna Responses WebSocket connection timed out")]
    ConnectionTimeout,
    /// The response did not contain an assistant text value.
    #[error("Luna response did not contain assistant output")]
    MissingOutput,
    /// The response exceeded the bounded output limit.
    #[error("Luna response exceeded the output limit")]
    OutputTooLarge,
    /// A newer classification replaced this request when the pool was full.
    #[error("Luna request was superseded by a newer classification")]
    Superseded,
}

struct PooledConnection {
    connection: ResponsesWebsocketConnection,
    // The bridge routes by thread ID, so each socket needs its own identity.

View on GitHub (pinned to 339751715c)

Solutions

  1. Read the nested ApiError: Api/QuotaExceeded/UsageNotIncluded are account or entitlement problems; Stream points at connection instability.
  2. Fix entitlements (model access, responses_websockets=2026-02-06 beta) or the request payload as indicated.
  3. For stream or transport errors, check proxies, custom CAs, and WebSocket upgrade support.
  4. Retry later for 5xx/429 once quotas reset.
Defensive patterns

Strategy: retry

Try / catch

match err {
    LunaSamplerError::Api(ApiError::Stream(_) | ApiError::ServerOverloaded | ApiError::Retryable { .. }) => retry_with_backoff().await,
    LunaSamplerError::Api(ApiError::Api { status, .. } | ApiError::Transport(TransportError::Http { status, .. }))
        if status.is_server_error() || status == StatusCode::TOO_MANY_REQUESTS => retry_with_backoff().await,
    _ => return Err(err),
}

Prevention

When it happens

Trigger: LunaSampler::sample/connect when the endpoint returns 4xx (quota exceeded, usage not included, invalid request, policy violations), the WS stream closes mid-response, or retryable errors persist past MAX_SAMPLING_RETRIES (2).

Common situations: Account lacks access to the classifier model or the responses_websockets beta; org quota hit; request rejected by content policy; flaky proxy killing upgraded connections.

Related errors


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/eb73e8f68eee4f29. Report an issue: GitHub.