fix: bound every await in the OpenAI/Codex reviewer path - #24
Merged
Conversation
Root causes of the silent 565s/750s review hangs: - buildClient left SDK defaults active: 600s timeout + 2 silent internal retries, multiplying with duul's own 3 attempts into up to 30min of unlogged waiting. Now timeout: 120s, maxRetries: 0 — duul owns retries. - isRetryable matched error.name === 'AbortError', but the SDK's APIUserAbortError/APIConnectionError keep name 'Error', so aborts and connection failures were never retried (hence zero Retry logs). - The Codex OAuth refresh fetch had no timeout and runs outside the review AbortController — a stalled endpoint hung the review forever. Now bounded at 30s via AbortSignal.timeout. - Stateless (ChatGPT backend) streams now race the 120s abort explicitly and abort the SDK stream controller in finally, covering mid-SSE stalls. - Log before each API call / tool execution and on the previously-silent tool-loop continue paths (cache hit, repeat limit, budget block). Adds a regression test that injects a hanging stream and asserts the call fails at the deadline instead of awaiting forever. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Reviews on the Codex/ChatGPT (stateless) backend went silent for 565s/750s with zero
[duul] Retrylogs — longer than the theoretical 363s worst case of duul's own 3×120s retry loop.Root causes
buildClientpassed notimeout/maxRetries, so the openai SDK used its defaults: 600s timeout + 2 silent internal retries. Multiplied by duul's 3 attempts, one flaky connection could burn up to 30 minutes with no log output. This is where the 565s/750s went.isRetryablematchederror.name === 'AbortError', but the SDK'sAPIUserAbortError/APIConnectionErrordon't overridename(it stays'Error'). So even when the 120s abort fired, the error was thrown immediately without retry — explaining the missing Retry logs.await this.refresh()runs outside the per-attempt AbortController and itsfetchhad no signal — a stalled OAuth endpoint hung the entire review forever, silently. This is the only code path consistent with an eternal single-await hang.Fix
timeout: 120_000, maxRetries: 0on the OpenAI client — duul's loop is the single owner of retries, and every retry is logged.error.name.AbortSignal.timeout(30_000)on the Codex token refresh fetch.Promise.raceagainst the 120s abort +stream.abort()infinally, as a backstop for mid-SSE stalls.→ openai stream (attempt 1/3, timeout 120000ms)) and each tool execution, plus the three previously-silent tool-loop continue paths (cache hit / repeat limit / budget block). A stall now names its await.Anthropic/Google providers are untouched: they use native
fetch, whose aborts throw realAbortErrorDOMExceptions, so their existing checks work.Test
New regression test injects a stream that ignores the request signal and never ends; asserts the call rejects at the review deadline and the stream is aborted. 97/97 tests pass.
🤖 Generated with Claude Code