@@ -257,6 +257,108 @@ describe("RTT harness", () => {
257257 }
258258 } ) ;
259259
260+ it ( "does not start another credential acquire after retry delay exhausts the deadline" , async ( ) => {
261+ let requests = 0 ;
262+ const server = createServer ( ( _request , response ) => {
263+ requests += 1 ;
264+ response . writeHead ( 503 , { "content-type" : "application/json" } ) ;
265+ response . end (
266+ JSON . stringify ( {
267+ status : "error" ,
268+ code : "POOL_EXHAUSTED" ,
269+ message : "credential pool exhausted" ,
270+ retryAfterMs : 1_000 ,
271+ } ) ,
272+ ) ;
273+ } ) ;
274+ const { port } = await listenOnLoopback ( server ) ;
275+ const tempDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "openclaw-rtt-credentials-retry-" ) ) ;
276+ tempDirs . push ( tempDir ) ;
277+ const startedAt = Date . now ( ) ;
278+
279+ try {
280+ await execFileAsync (
281+ process . execPath ,
282+ [
283+ CREDENTIAL_SCRIPT_PATH ,
284+ "acquire" ,
285+ "--lease-file" ,
286+ path . join ( tempDir , "lease.json" ) ,
287+ "--credential-env-file" ,
288+ path . join ( tempDir , "credentials.env" ) ,
289+ ] ,
290+ {
291+ env : {
292+ ...credentialBrokerEnv ( port ) ,
293+ OPENCLAW_QA_CREDENTIAL_ACQUIRE_TIMEOUT_MS : "75" ,
294+ OPENCLAW_QA_CREDENTIAL_HTTP_TIMEOUT_MS : "250" ,
295+ } ,
296+ maxBuffer : 128 * 1024 ,
297+ } ,
298+ ) ;
299+ throw new Error ( "Expected credential acquire to fail." ) ;
300+ } catch ( error ) {
301+ const execError = error as Error & { stderr ?: string } ;
302+ expect ( execError . stderr ) . toContain ( "credential broker acquire timed out after 75ms" ) ;
303+ expect ( Date . now ( ) - startedAt ) . toBeLessThan ( 500 ) ;
304+ expect ( requests ) . toBe ( 1 ) ;
305+ } finally {
306+ await closeServer ( server ) ;
307+ }
308+ } ) ;
309+
310+ it ( "caps credential acquire HTTP retries to the remaining acquire deadline" , async ( ) => {
311+ let requests = 0 ;
312+ const server = createServer ( ( _request , response ) => {
313+ requests += 1 ;
314+ if ( requests === 1 ) {
315+ response . writeHead ( 503 , { "content-type" : "application/json" } ) ;
316+ response . end (
317+ JSON . stringify ( {
318+ status : "error" ,
319+ code : "POOL_EXHAUSTED" ,
320+ message : "credential pool exhausted" ,
321+ retryAfterMs : 1 ,
322+ } ) ,
323+ ) ;
324+ }
325+ } ) ;
326+ const { port } = await listenOnLoopback ( server ) ;
327+ const tempDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "openclaw-rtt-credentials-cap-" ) ) ;
328+ tempDirs . push ( tempDir ) ;
329+ const startedAt = Date . now ( ) ;
330+
331+ try {
332+ await execFileAsync (
333+ process . execPath ,
334+ [
335+ CREDENTIAL_SCRIPT_PATH ,
336+ "acquire" ,
337+ "--lease-file" ,
338+ path . join ( tempDir , "lease.json" ) ,
339+ "--credential-env-file" ,
340+ path . join ( tempDir , "credentials.env" ) ,
341+ ] ,
342+ {
343+ env : {
344+ ...credentialBrokerEnv ( port ) ,
345+ OPENCLAW_QA_CREDENTIAL_ACQUIRE_TIMEOUT_MS : "100" ,
346+ OPENCLAW_QA_CREDENTIAL_HTTP_TIMEOUT_MS : "900" ,
347+ } ,
348+ maxBuffer : 128 * 1024 ,
349+ } ,
350+ ) ;
351+ throw new Error ( "Expected credential acquire to fail." ) ;
352+ } catch ( error ) {
353+ const execError = error as Error & { stderr ?: string } ;
354+ expect ( execError . stderr ) . toContain ( "credential broker acquire timed out after" ) ;
355+ expect ( Date . now ( ) - startedAt ) . toBeLessThan ( 500 ) ;
356+ expect ( requests ) . toBe ( 2 ) ;
357+ } finally {
358+ await closeServer ( server ) ;
359+ }
360+ } ) ;
361+
260362 it ( "preserves empty broker responses for successful lease release" , async ( ) => {
261363 const server = createServer ( ( _request , response ) => {
262364 response . writeHead ( 204 ) ;
0 commit comments