11import { describe , expect , test } from "bun:test" ;
22import type { AdmissionQueue } from "../subagent/admission.js" ;
3- import { createCorbitsRetryPolicy , type CorbitsRetryPolicyOptions } from "./retry-policy.js" ;
3+ import {
4+ createCorbitsRetryPolicy ,
5+ MAX_BLIND_WAIT_MS ,
6+ type CorbitsRetryPolicyOptions ,
7+ } from "./retry-policy.js" ;
48
59const HTML_503 = `<!DOCTYPE html><html><body>503 Service Unavailable Cloudflare</body></html>` ;
610
@@ -104,8 +108,9 @@ describe("createCorbitsRetryPolicy", () => {
104108 raw : { error : { message : "Too Many Requests" } } ,
105109 } ,
106110 } ) ;
107- // Remapped to retryable -> default backoff, not abort on moderate Retry-After.
108- expect ( decision ) . toEqual ( { kind : "retry" , delayMs : 500 } ) ;
111+ // Remapped to retryable -> paced retry capped at the blind-wait ceiling,
112+ // not abort on moderate Retry-After.
113+ expect ( decision ) . toEqual ( { kind : "retry" , delayMs : MAX_BLIND_WAIT_MS } ) ;
109114 } ) ;
110115
111116 test ( "stamped Codex usage-limit 429 retries as retryable, not long-quota abort" , async ( ) => {
@@ -120,7 +125,7 @@ describe("createCorbitsRetryPolicy", () => {
120125 raw : "You have hit your ChatGPT usage limit" ,
121126 } ,
122127 } ) ;
123- expect ( decision ) . toEqual ( { kind : "retry" , delayMs : 500 } ) ;
128+ expect ( decision ) . toEqual ( { kind : "retry" , delayMs : MAX_BLIND_WAIT_MS } ) ;
124129 } ) ;
125130
126131 test ( "stamped xAI usage/quota body still aborts on long retryAfterMs" , async ( ) => {
@@ -174,7 +179,7 @@ describe("createCorbitsRetryPolicy", () => {
174179 } ;
175180 expect ( await decide ( bare429 ) ) . toEqual ( { kind : "abort" } ) ;
176181 current = "xai/thegreataxios" ;
177- expect ( await decide ( bare429 ) ) . toEqual ( { kind : "retry" , delayMs : 500 } ) ;
182+ expect ( await decide ( bare429 ) ) . toEqual ( { kind : "retry" , delayMs : MAX_BLIND_WAIT_MS } ) ;
178183 } ) ;
179184
180185 // CL-6910: the harness only surfaces `inference.error` to the director
@@ -242,7 +247,7 @@ describe("createCorbitsRetryPolicy", () => {
242247 raw : { error : { message : "Too Many Requests" } } ,
243248 } ,
244249 } ;
245- expect ( await decide ( bare429 ) ) . toEqual ( { kind : "retry" , delayMs : 500 } ) ;
250+ expect ( await decide ( bare429 ) ) . toEqual ( { kind : "retry" , delayMs : MAX_BLIND_WAIT_MS } ) ;
246251 current = "openai" ;
247252 expect ( await decide ( bare429 ) ) . toEqual ( { kind : "abort" } ) ;
248253 } ) ;
@@ -298,4 +303,48 @@ describe("createCorbitsRetryPolicy", () => {
298303 } ) ;
299304 expect ( notes ) . toHaveLength ( 0 ) ;
300305 } ) ;
306+
307+ test ( "retryable 429 honors Retry-After instead of the fixed 500/1000ms backoff" , async ( ) => {
308+ const decide = policy ( { providerId : "codex/abk-labs" } ) ;
309+ const situation = ( attempt : number ) => ( {
310+ attempt,
311+ elapsedMs : 0 ,
312+ error : {
313+ category : "retryable" as const ,
314+ message : "Too Many Requests" ,
315+ statusCode : 429 ,
316+ retryAfterMs : 5_000 ,
317+ } ,
318+ } ) ;
319+ expect ( await decide ( situation ( 1 ) ) ) . toEqual ( { kind : "retry" , delayMs : 5_000 } ) ;
320+ expect ( await decide ( situation ( 2 ) ) ) . toEqual ( { kind : "retry" , delayMs : 5_000 } ) ;
321+ expect ( await decide ( situation ( 3 ) ) ) . toEqual ( { kind : "abort" } ) ;
322+ } ) ;
323+
324+ test ( "retryable 429 caps a long Retry-After at the blind-wait ceiling" , async ( ) => {
325+ const decide = policy ( { providerId : "codex/abk-labs" } ) ;
326+ const decision = await decide ( {
327+ attempt : 1 ,
328+ elapsedMs : 0 ,
329+ error : {
330+ category : "retryable" as const ,
331+ message : "Too Many Requests" ,
332+ statusCode : 429 ,
333+ retryAfterMs : 120_000 ,
334+ } ,
335+ } ) ;
336+ expect ( decision ) . toEqual ( { kind : "retry" , delayMs : MAX_BLIND_WAIT_MS } ) ;
337+ } ) ;
338+
339+ test ( "retryable 429 without Retry-After keeps the fixed backoff" , async ( ) => {
340+ const decide = policy ( ) ;
341+ const situation = ( attempt : number ) => ( {
342+ attempt,
343+ elapsedMs : 0 ,
344+ error : { category : "retryable" as const , message : "boom" , statusCode : 429 } ,
345+ } ) ;
346+ expect ( await decide ( situation ( 1 ) ) ) . toEqual ( { kind : "retry" , delayMs : 500 } ) ;
347+ expect ( await decide ( situation ( 2 ) ) ) . toEqual ( { kind : "retry" , delayMs : 1000 } ) ;
348+ expect ( await decide ( situation ( 3 ) ) ) . toEqual ( { kind : "abort" } ) ;
349+ } ) ;
301350} ) ;
0 commit comments