Skip to content

[Bug]: Safety block triggers repeated Codex App reconnects #5176

Closed

Description

@Rizek000

Summary

Codex App repeatedly shows Reconnecting …/5 after an upstream safety block interrupts a response. Expected: surface the block as a terminal error rather than repeatedly reconnecting.

Environment

  • Client: Codex App; bundled CLI 0.155.0-alpha.2.6
  • Area: Streaming / proxy error handling
  • OpenCodex: 2.59.0; Bun 1.4.0
  • OS: macOS 26.6
  • Provider/model: openai / gpt-6-astra, WebSocket transport; account pooling enabled

Reproduction

Send a request from Codex App through OpenCodex using the configuration above. Intermittently, output begins, then stops with repeated reconnect attempts. No deterministic minimal prompt identified.

Error and evidence

stream disconnected before completion: This request was blocked by our safety systems. Reason: Potentially unintended activity.

In a 400-request log sample, 44 requests had this error; 27 recorded output before failure. All 44 were logged as HTTP 502, errorCode: upstream_server_error.

The upstream block's cause is unknown; direct-client behavior has not been compared. Could the error mapping cause a terminal rejection to be treated as retryable?

Checks

  • Searched existing issues and documentation.
  • Removed credentials, account identifiers, prompts, and personal data.

Activity

  1. lidge-jun commented on Sep 19, 2026

    @lidge-jun
    Owner

    Closed by #5200, squash-merged into dev as 3fd293d841.

    The mapping hypothesis in this report was right, and it is worth saying exactly how it was confirmed rather than just asserting it.

    Codex classifies a response.failed by error.code alone. Its fatal set is seven codes — context_length_exceeded, insufficient_quota, usage_not_included, cyber_policy, misalignment_policy_violation, invalid_prompt, bio_policy — and everything outside that set is retried. CodexErr::is_retryable confirms the same split from the other side. Codex also has no handler for a bare SSE error event at all, which makes the terminal this proxy synthesizes the only channel a refusal can travel through.

    That channel was discarding the verdict. upstreamErrorTailFrame in src/server/relay.ts stamped upstream_server_error on every synthesized terminal unconditionally. The upstream's message survived, so the client displayed the refusal text while treating the turn as a retryable disconnect — which is exactly the "shows the block, then reconnects" behaviour reported here, and why all 44 requests in the sample logged as HTTP 502 with upstream_server_error.

    The fix is classification, not circumvention. A structured code decides on its own when the upstream sends one; a recognized refusal phrase is read only when there is no code at all. Code and message are read from the same candidate priority, so the envelope that supplied the message also supplies the verdict. The refusal set was narrowed to the four content refusals; insufficient_quota and context_length_exceeded stay on their existing quota and context paths. Without a refusal code the terminal is byte-identical to before, so a genuine transport failure remains retryable — there is a regression asserting precisely that.

    Four rounds of parallel adversarial review all returned FAIL on the first pass and found five real defects independently: a non-refusal code being overridden by message matching, a code-only refusal with no message falling through to adapter_eof, a refusal nested under response.error invisible to the code scan, a read failure after a refusal being overwritten with upstream_reset and retried, and a low-priority nested refusal beating a high-priority transient code. All five are fixed with regressions.

    One thing was deliberately left out of scope: the request-log accounting that records these as 502. Changing it surfaced a separate defect where an earlier bare error latches a verdict a later genuine terminal should set, and that could not be verified without running locally, which this lane is prohibited from doing.

    Evidence is exact-head hosted CI at 614fbd76829ad9b4c1d04b030a1c92d637549554: test 1/4 through 4/4 and gates all successful, with the lane's regressions running on those Linux shards. No local suite was run.

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 working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions