Skip to content

[Feature]: opt-in transient-5xx retry for key-auth providers on openai-chat adapter #2643

Closed

Description

@tone-leh

Area

Provider adapters

What are you trying to accomplish?

When a custom provider (e.g. an internal corporate LLM gateway) returns transient 5xx errors (502/503/504), the proxy should retry the request with exponential backoff before surfacing the error to the client. Currently this only works for the Google AI Studio adapter (fetchWithTransientRetry is gated behind route.provider.adapter === "google" in responses/core.ts), but it should be available to any key-auth provider via opt-in configuration.

What prevents this today?

The #1851 scope guard in src/server/responses/core.ts (lines 4597-4601 and 5059-5061) deliberately limits fetchWithTransientRetry to adapter === "google". Every other adapter keeps fetchWithResetRetry which only retries ECONNRESET/EPIPE connection resets, not HTTP 5xx responses. This means:

  • A 503 server_is_overloaded from a custom provider kills the turn immediately instead of waiting and retrying.
  • Users of providers that experience transient overload (e.g. shared corporate gateways) see sessions die mid-way instead of surviving a burst.

The retryOn429 config exists for 429 rate limiting but covers a different failure class. There is no mechanism to opt into transient-5xx retry for non-Google adapters.

What should OpenCodex do?

Introduce an opt-in provider config field (e.g. transientRetryOn5xx: { enabled: true, attempts?: number, baseDelayMs?: number, maxDelayMs?: number }) that allows any key-auth provider to enable fetchWithTransientRetry on the openai-chat adapter path. When enabled, the proxy should:

  1. Catch transient upstream statuses (500/502/503/504/520/521/522) on the initial request before streaming starts.
  2. Wait with exponential backoff (respecting Retry-After header), capped by a configurable max delay.
  3. Retry the identical request up to the configured attempts.
  4. Only surface the error to the client if all retries are exhausted.

The retry should be opt-in (disabled by default) so existing providers are not affected. The fetchWithTransientRetry implementation already handles Retry-After headers, heartbeats to keep stall watchdogs fed, and slow-attempt detection (returning a slow 502 as-is rather than retrying past the client timeout).

The same treatment should also apply to the chat-native.ts path (/v1/chat/completions), which currently uses fetchWithResetRetry for all non-Google adapters.

Example usage or interface

```json
// ~/.opencodex/config.json
{
"providers": {
"my-gateway": {
"adapter": "openai-chat",
"baseUrl": "http://gateway.internal/v1",
"authMode": "key",
"apiKey": "sk-...",
"transientRetryOn5xx": {
"enabled": true,
"attempts": 3,
"maxDelayMs": 5000
}
}
}
}
```

Before: A 503 from gateway.internal immediately kills the Codex turn with "Provider error 503".

After: The proxy waits ~400ms, retries; if another 503, waits ~800ms, retries again; only then surfaces the error. The turn survives transient overload.

Alternatives or workarounds

  1. Wrap the provider in a local retry proxy (e.g. nginx or a small Node script). This adds operational complexity and splits the timeout budget between two hops.
  2. Use a different adapter. Not applicable — openai-chat is the correct adapter for OpenAI-compatible endpoints.
  3. Rely on retryOn429. Inapplicable — 429 is rate-limiting, not server overload. Many corporate gateways return 503 under load rather than 429.

Activity

  1. added
    enhancementNew feature or request
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    on Aug 26, 2026
  2. lidge-jun commented on Aug 26, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 70 / 80

    설명

    이 이슈는 OpenAI 호환 openai-chat 제공자가 500, 502, 503, 504 같은 잠깐의 서버 오류를 돌려줄 때, 같은 요청을 짧게 기다렸다가 다시 보내게 해 달라는 제안입니다. 예를 들어 사내 LLM 게이트웨이가 몇 초 동안 바쁘기만 한데도 현재는 한 번의 503으로 대화가 끝날 수 있습니다. 기본값은 꺼 둔 채 제공자별로 켤 수 있게 하자는 내용이라서, 기존 사용자의 동작을 몰래 바꾸지 않는다는 점도 좋습니다.

    현재 dev에서 이 차이는 실제로 확인됩니다. src/server/responses/core.ts 4899-4918줄은 일반 어댑터 경로에서 google일 때만 fetchWithTransientRetry를 고르고, 나머지는 연결 끊김만 다시 시도하는 fetchWithResetRetry를 고릅니다. 같은 파일 5412-5435줄의 후속 요청도 똑같습니다. 따라서 보고된 사내 openai-chat 제공자의 503은 이 재시도 층을 쓰지 못합니다.

    재사용할 핵심 코드는 이미 있습니다. src/lib/upstream-retry.ts 348-391줄의 fetchWithTransientRetry는 응답을 클라이언트에 보내기 전까지만 재시도하고, 재시도할 응답 본문을 취소하며, Retry-After도 읽습니다. 368-370줄은 성공 응답, 재시도 대상이 아닌 상태, 취소된 요청, 너무 오래 걸린 시도를 즉시 돌려줍니다. 그래서 새 알고리즘을 따로 만들기보다, 안전한 설정 경계와 호출 조건을 추가하는 일이 중심이 되어야 합니다.

    다만 이슈의 설정 예시를 그대로 넣기 전에 범위를 조금 더 또렷하게 정해야 합니다. 현재 TransientRetryOptions는 src/lib/upstream-retry.ts 245-248줄에서 attempts와 테스트용 slowAttemptMs만 더하며, 실제 400ms와 5초 값은 같은 파일 25-30줄 및 374-377줄의 상수입니다. 즉 baseDelayMs와 maxDelayMs까지 사용자 설정으로 열려면 옵션 타입, 상한 검증, 런타임 전달을 함께 바꿔야 합니다. 첫 단계는 시도 횟수만 제한적으로 열고 지연값은 기존 안전 상수를 쓰는 편이 작고 안전합니다.

    설정도 한 곳만 추가하면 끝나지 않습니다. 제공자 타입은 src/types/provider.ts 533-539줄의 retryOn429처럼 명시해야 하고, 로드 검증은 src/config.ts 485-519줄의 제공자 스키마에 들어가야 합니다. 관리 API로 수정할 수 있게 할지 결정한다면 src/server/management/provider-routes.ts 112-366줄의 PATCH 필드 마스크에도 검증과 null 삭제 규칙이 필요합니다. 손으로 쓴 잘못된 값이 조용히 켜지는 일이 없도록, enabled: false와 값 부재를 모두 확실히 비활성으로 처리해야 합니다.

    /v1/chat/completions도 빠뜨리면 안 됩니다. src/server/chat-native.ts 205-227줄의 send는 지금 언제나 fetchWithResetRetry를 사용합니다. 여기까지 같은 정책을 연결해야 Responses 경로와 Chat Completions 경로가 같은 설정인데 서로 다르게 움직이지 않습니다. 또한 키 교체나 조합 경로가 있는 경우에는 같은 대상 재시도를 먼저 몇 번 할지, 첫 5xx에서 다음 대상으로 넘어갈지를 테스트로 고정해야 합니다. 현재 src/server/responses/core.ts 4899-4902줄 주석도 조합마다 약 1.2초를 소비하지 않으려는 이유를 분명히 적고 있습니다.

    경로/심볼 - 제안한 transientRetryOn5xx의 정확한 타입, 기본값, 숫자 상한과 잘못된 설정의 처리 방식이 아직 정해지지 않았습니다.
    src/lib/upstream-retry.ts:245 - TransientRetryOptions에는 제안한 baseDelayMs와 maxDelayMs를 받을 공개 옵션이 없습니다.
    src/server/responses/core.ts:4903 - 현재 선택 조건은 google만 보고 있어, 새 설정을 켠 키 인증 openai-chat 제공자가 재시도를 사용하지 못합니다.
    src/server/responses/core.ts:5414 - 후속 요청에도 같은 제한이 있어 초기 요청만 고치면 동작이 서로 달라집니다.
    src/server/chat-native.ts:207 - Chat Completions 경로는 항상 reset-only 재시도를 사용하므로 별도 연결이 필요합니다.
    src/server/management/provider-routes.ts:112 - 관리 PATCH로 이 설정을 다룰 계획이라면 필드 마스크와 null 삭제 처리가 아직 없습니다.

    메인테이너의 판단이 필요한 지점

    • 첫 버전에서 사용자가 조절할 값을 enabled와 attempts까지만 둘지, 지연 시간까지 열지 결정해야 합니다.
    • 조합 라우팅과 여러 키가 있을 때 같은 대상 5xx 재시도를 먼저 할지, 즉시 다음 대상으로 넘길지 결정해야 합니다.
    • 키 인증 openai-chat에만 허용할지, 안전성이 확인된 다른 일반 어댑터에도 같은 설정을 허용할지 결정해야 합니다.
    • 관리 API PATCH에서도 설정을 편집·삭제하게 할지, 우선 config.json 전용으로 둘지 결정해야 합니다.

    너의 추천

    이 이슈는 유지하고 구현 우선순위를 높게 두는 것을 추천합니다. 첫 PR은 기본 꺼짐, 키 인증 openai-chat 한정, 기존 고정 지연값 재사용, 제한된 attempts만 지원하는 작은 범위로 시작하세요. src/server/responses/core.ts의 초기·후속 전송과 src/server/chat-native.ts를 모두 연결하고, 503 뒤 성공, 재시도 소진, Retry-After, 취소, 느린 첫 시도, 조합/키 교체 순서를 테스트한 뒤 지연값 사용자 설정은 별도 후속으로 여는 것이 안전합니다.

    이 댓글은 grok-bot이 작성했습니다

  3. Ingwannu commented on Aug 26, 2026

    @Ingwannu
    Owner

    Confirmed as a real opt-in compatibility need. PR #2655 is the active implementation, so this issue should remain open rather than spawning a duplicate patch.

    I reviewed #2655 exact head 5f75c5ac951c3a0971aaafcc686f65a1d4ebd418 and requested changes for a concrete retry-budget bug: the configured attempts value is shared by the outer transient-status loop and each inner connection-reset loop. attempts: 3 can therefore send 9 upstream requests, and the allowed maximum can reach 100. The PR also still needs terminal-continuation and native Chat coverage, user-facing docs, a current-dev rebase, and exact-head CI.

    The intended first version remains narrow: disabled by default, key-auth only, fixed safe delay constants, and a hard total-send bound across reset plus 5xx recovery. I will keep this issue open until that bounded implementation lands on dev.

  4. added 5 commits that reference this issue on Aug 29, 2026
    b83fe34
    a26ceac
    f4efa50
    36440dc
    607042b
  5. lidge-jun commented on Aug 30, 2026

    @lidge-jun
    Owner

    Implemented on dev via #2981 as 607042b.

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

    enhancementNew feature or requestproviderProvider adapters, OpenAI-compat presets, upstream API quirks

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions