You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
oauth/xai: postXaiToken retries a request the caller already aborted when abort() carries a custom reason #4047
The early-exit guard depends on the rejection being a DOMException named AbortError. That holds for a bare controller.abort(), but when a caller aborts with a reason, fetch (undici, and Bun follows the same spec) rejects with that reason object as-is:
Measured on Node 24.17. In that case isAbortError returns false, the guard is skipped, and the loop sleeps and retries up to two more times on a signal that is already aborted. AbortSignal.timeout() expiry (name === "TimeoutError") is likewise not treated as terminal, so a 30s token timeout can stretch to ~90s across three attempts.
Fix
Check the signal, not the error's class:
}catch(error){if(signal?.aborted)throwerror;// any reasonif((erroras{name?: string})?.name==="TimeoutError")throwerror;
...
}
Repro
Pass signal from a controller, call controller.abort(new Error("x")) before the first fetch resolves, inject deps.sleep that records calls: sleep is invoked twice instead of zero times.
Found while porting this module into ima2-gen (devlog 260909_grok_native_oauth/001_opencodex_port_spec.md D3).
이 이슈는 postXaiToken이 이미 abort된 요청을 두 번 더 재시도할 수 있다는 버그입니다. 지금 dev HEAD cfff026src/oauth/xai.ts를 보면 isAbortError는 DOMException이고 이름이 AbortError일 때만 참입니다(97행). catch 쪽(114행 근처)은 isAbortError(error) && signal?.aborted일 때만 바로 throw하고, 아니면 sleep 후 continue합니다.
문제는 호출자가 controller.abort(new Error("user cancel"))처럼 reason을 실어 abort하면, undici/Bun fetch는 그 reason 객체를 그대로 reject한다는 점입니다. 그때 instanceof DOMException이 false라 가드가 빠지고, 이미 죽은 signal로 최대 2번 더 sleep+retry합니다. 이슈가 Node 24.17에서 확인했습니다. 같은 이유로 AbortSignal.timeout()의 TimeoutError도 터미널로 안 보고, 30초 타임아웃이 재시도 때문에 ~90초까지 늘어날 수 있습니다. requestSignal이 이미 AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS)와 호출자 signal을 AbortSignal.any로 묶고 있어서, 타임아웃 경로가 실제로 여기로 들어옵니다.
이건 사용자 취소·타임아웃 UX와 직접 닿습니다. Grok 로그인/리프레시 중 취소했는데 백그라운드가 계속 두드리면 로그·레이트·오류 메시지가 지저분해집니다. #4045/#4046과 같은 모듈이라 한 패치에 넣기 좋고, types/config 분할과도 무관합니다.
제안 수정은 에러 클래스가 아니라 signal?.aborted와 TimeoutError 이름을 보는 쪽입니다. 그게 fetch 구현 차이를 덜 탑니다.
라인 97 · isAbortError - reason 있는 abort와 TimeoutError를 놓칩니다. DOMException AbortError만 통과합니다.
라인 114 · catch 가드 - isAbortError && signal?.aborted 이중 조건 때문에, abort인데 에러 shape이 다르면 재시도 루프로 떨어집니다.
경로 requestSignal + 3회 루프 - 타임아웃 한 번이 세 번으로 늘어날 수 있어, UI/CLI 대기 상한이 깨집니다.
메인테이너의 판단이 필요한 지점
TimeoutError를 항상 터미널로 볼지, 호출자 signal abort만 터미널로 볼지
Activity
lidge-jun commentedon Sep 8, 2026
리뷰 · 우선순위 64 / 80
이 이슈는
postXaiToken이 이미 abort된 요청을 두 번 더 재시도할 수 있다는 버그입니다. 지금devHEAD cfff026src/oauth/xai.ts를 보면isAbortError는DOMException이고 이름이AbortError일 때만 참입니다(97행). catch 쪽(114행 근처)은isAbortError(error) && signal?.aborted일 때만 바로 throw하고, 아니면 sleep 후 continue합니다.문제는 호출자가
controller.abort(new Error("user cancel"))처럼 reason을 실어 abort하면, undici/Bun fetch는 그 reason 객체를 그대로 reject한다는 점입니다. 그때instanceof DOMException이 false라 가드가 빠지고, 이미 죽은 signal로 최대 2번 더 sleep+retry합니다. 이슈가 Node 24.17에서 확인했습니다. 같은 이유로AbortSignal.timeout()의TimeoutError도 터미널로 안 보고, 30초 타임아웃이 재시도 때문에 ~90초까지 늘어날 수 있습니다.requestSignal이 이미AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS)와 호출자 signal을AbortSignal.any로 묶고 있어서, 타임아웃 경로가 실제로 여기로 들어옵니다.이건 사용자 취소·타임아웃 UX와 직접 닿습니다. Grok 로그인/리프레시 중 취소했는데 백그라운드가 계속 두드리면 로그·레이트·오류 메시지가 지저분해집니다. #4045/#4046과 같은 모듈이라 한 패치에 넣기 좋고, types/config 분할과도 무관합니다.
제안 수정은 에러 클래스가 아니라
signal?.aborted와TimeoutError이름을 보는 쪽입니다. 그게 fetch 구현 차이를 덜 탑니다.라인 97 · isAbortError - reason 있는 abort와 TimeoutError를 놓칩니다. DOMException AbortError만 통과합니다.
라인 114 · catch 가드 -
isAbortError && signal?.aborted이중 조건 때문에, abort인데 에러 shape이 다르면 재시도 루프로 떨어집니다.경로 requestSignal + 3회 루프 - 타임아웃 한 번이 세 번으로 늘어날 수 있어, UI/CLI 대기 상한이 깨집니다.
메인테이너의 판단이 필요한 지점
너의 추천
수락하세요. 세 xAI OAuth 이슈 중 체감 위험은 이쪽이 큽니다.
signal?.aborted우선 가드 + TimeoutError 조기 throw + sleep 호출 횟수 테스트가 최소 세트입니다.이 댓글은 grok-bot이 작성했습니다
lidge-jun commentedon Sep 9, 2026
Fixed on
devvia #4087 at 4a08c75.devlog: plan the xAI OAuth retry hardening unit (lidge-jun#4045, lidg…
oauth/xai: honor Retry-After and stop retrying aborted token requests (…
devlog: plan the xAI OAuth retry hardening unit (lidge-jun#4045, lidg…
oauth/xai: honor Retry-After and stop retrying aborted token requests (…