Repository navigation
[Feature]: opt-in transient-5xx retry for key-auth providers on openai-chat adapter #2643
Description
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:
- Catch transient upstream statuses (500/502/503/504/520/521/522) on the initial request before streaming starts.
- Wait with exponential backoff (respecting Retry-After header), capped by a configurable max delay.
- Retry the identical request up to the configured attempts.
- 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
- 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.
- Use a different adapter. Not applicable — openai-chat is the correct adapter for OpenAI-compatible endpoints.
- Rely on retryOn429. Inapplicable — 429 is rate-limiting, not server overload. Many corporate gateways return 503 under load rather than 429.
Activity
lidge-jun commentedon Aug 26, 2026
리뷰 · 우선순위 70 / 80
설명
이 이슈는 OpenAI 호환
openai-chat제공자가 500, 502, 503, 504 같은 잠깐의 서버 오류를 돌려줄 때, 같은 요청을 짧게 기다렸다가 다시 보내게 해 달라는 제안입니다. 예를 들어 사내 LLM 게이트웨이가 몇 초 동안 바쁘기만 한데도 현재는 한 번의 503으로 대화가 끝날 수 있습니다. 기본값은 꺼 둔 채 제공자별로 켤 수 있게 하자는 내용이라서, 기존 사용자의 동작을 몰래 바꾸지 않는다는 점도 좋습니다.현재
dev에서 이 차이는 실제로 확인됩니다.src/server/responses/core.ts4899-4918줄은 일반 어댑터 경로에서google일 때만fetchWithTransientRetry를 고르고, 나머지는 연결 끊김만 다시 시도하는fetchWithResetRetry를 고릅니다. 같은 파일 5412-5435줄의 후속 요청도 똑같습니다. 따라서 보고된 사내openai-chat제공자의 503은 이 재시도 층을 쓰지 못합니다.재사용할 핵심 코드는 이미 있습니다.
src/lib/upstream-retry.ts348-391줄의fetchWithTransientRetry는 응답을 클라이언트에 보내기 전까지만 재시도하고, 재시도할 응답 본문을 취소하며,Retry-After도 읽습니다. 368-370줄은 성공 응답, 재시도 대상이 아닌 상태, 취소된 요청, 너무 오래 걸린 시도를 즉시 돌려줍니다. 그래서 새 알고리즘을 따로 만들기보다, 안전한 설정 경계와 호출 조건을 추가하는 일이 중심이 되어야 합니다.다만 이슈의 설정 예시를 그대로 넣기 전에 범위를 조금 더 또렷하게 정해야 합니다. 현재
TransientRetryOptions는src/lib/upstream-retry.ts245-248줄에서attempts와 테스트용slowAttemptMs만 더하며, 실제 400ms와 5초 값은 같은 파일 25-30줄 및 374-377줄의 상수입니다. 즉baseDelayMs와maxDelayMs까지 사용자 설정으로 열려면 옵션 타입, 상한 검증, 런타임 전달을 함께 바꿔야 합니다. 첫 단계는 시도 횟수만 제한적으로 열고 지연값은 기존 안전 상수를 쓰는 편이 작고 안전합니다.설정도 한 곳만 추가하면 끝나지 않습니다. 제공자 타입은
src/types/provider.ts533-539줄의retryOn429처럼 명시해야 하고, 로드 검증은src/config.ts485-519줄의 제공자 스키마에 들어가야 합니다. 관리 API로 수정할 수 있게 할지 결정한다면src/server/management/provider-routes.ts112-366줄의 PATCH 필드 마스크에도 검증과null삭제 규칙이 필요합니다. 손으로 쓴 잘못된 값이 조용히 켜지는 일이 없도록,enabled: false와 값 부재를 모두 확실히 비활성으로 처리해야 합니다./v1/chat/completions도 빠뜨리면 안 됩니다.src/server/chat-native.ts205-227줄의send는 지금 언제나fetchWithResetRetry를 사용합니다. 여기까지 같은 정책을 연결해야 Responses 경로와 Chat Completions 경로가 같은 설정인데 서로 다르게 움직이지 않습니다. 또한 키 교체나 조합 경로가 있는 경우에는 같은 대상 재시도를 먼저 몇 번 할지, 첫 5xx에서 다음 대상으로 넘어갈지를 테스트로 고정해야 합니다. 현재src/server/responses/core.ts4899-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까지만 둘지, 지연 시간까지 열지 결정해야 합니다.openai-chat에만 허용할지, 안전성이 확인된 다른 일반 어댑터에도 같은 설정을 허용할지 결정해야 합니다.너의 추천
이 이슈는 유지하고 구현 우선순위를 높게 두는 것을 추천합니다. 첫 PR은 기본 꺼짐, 키 인증
openai-chat한정, 기존 고정 지연값 재사용, 제한된attempts만 지원하는 작은 범위로 시작하세요.src/server/responses/core.ts의 초기·후속 전송과src/server/chat-native.ts를 모두 연결하고, 503 뒤 성공, 재시도 소진,Retry-After, 취소, 느린 첫 시도, 조합/키 교체 순서를 테스트한 뒤 지연값 사용자 설정은 별도 후속으로 여는 것이 안전합니다.이 댓글은 grok-bot이 작성했습니다
Ingwannu commentedon Aug 26, 2026
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
5f75c5ac951c3a0971aaafcc686f65a1d4ebd418and requested changes for a concrete retry-budget bug: the configuredattemptsvalue is shared by the outer transient-status loop and each inner connection-reset loop.attempts: 3can 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-devrebase, 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.feat(providers): add opt-in transient-5xx retry policy for key-auth p…
feat(providers): add opt-in transient-5xx retry for key-auth openai-chat
fix(upstream-retry): make attempts one total-send budget across both …
feat(providers): add opt-in transient-5xx retry for key-auth openai-chat
feat(providers): add opt-in transient-5xx retry with a shared total-s…
lidge-jun commentedon Aug 30, 2026
Implemented on
devvia #2981 as 607042b.feat(providers): add opt-in transient-5xx retry with a shared total-s…
feat(providers): add opt-in transient-5xx retry with a shared total-s…