@@ -113,7 +113,7 @@ const status = SessionStatus.layer.pipe(Layer.provideMerge(Bus.layer))
113113const run = SessionRunState . layer . pipe ( Layer . provide ( status ) )
114114const infra = Layer . mergeAll ( NodeFileSystem . layer , CrossSpawnSpawner . defaultLayer )
115115
116- // Copied verbatim from `prompt-effect .test.ts` — that file exports nothing,
116+ // Copied verbatim from `prompt.test.ts` — that file exports nothing,
117117// so we can't import the helper. Keeping the composition identical guarantees
118118// this regression gate exercises the same service wiring the rest of the
119119// loop tests do (real Session/SessionPrompt/ToolRegistry/Question/Permission,
@@ -253,9 +253,9 @@ it.live(
253253 // transitions to retry, the hang regression is back. `pollUnsafe` is
254254 // the public synchronous-peek API — we use it to short-circuit if
255255 // the fiber dies early so the error cause surfaces, rather than
256- // timing out blindly at 8s .
256+ // timing out blindly at 12s .
257257 const observed = yield * Effect . gen ( function * ( ) {
258- const end = Date . now ( ) + 8_000
258+ const end = Date . now ( ) + 12_000
259259 while ( Date . now ( ) < end ) {
260260 const exit = fiber . pollUnsafe ( )
261261 if ( exit ) return yield * Effect . fail ( new Error ( `loop exited before retry observed: ${ JSON . stringify ( exit ) } ` ) )
@@ -264,21 +264,26 @@ it.live(
264264 yield * Effect . sleep ( "25 millis" )
265265 }
266266 const snap = yield * sessionStatus . get ( chat . id )
267+ if ( snap . type === "retry" ) return snap
267268 return yield * Effect . fail (
268- new Error ( `expected retry status within 8s ; last status: ${ JSON . stringify ( snap ) } ` ) ,
269+ new Error ( `expected retry status within 12s ; last status: ${ JSON . stringify ( snap ) } ` ) ,
269270 )
270- } )
271+ } ) . pipe (
272+ Effect . ensuring (
273+ Effect . gen ( function * ( ) {
274+ yield * prompt . cancel ( chat . id ) . pipe ( Effect . timeout ( "1 second" ) , Effect . ignore )
275+ yield * Fiber . interrupt ( fiber ) . pipe ( Effect . timeout ( "3 seconds" ) , Effect . ignore )
276+ } ) ,
277+ ) ,
278+ )
271279
272280 expect ( observed . type ) . toBe ( "retry" )
273281 expect ( observed . attempt ) . toBeGreaterThanOrEqual ( 1 )
274282 // SessionRetry.transportMessage populates the retry message from
275283 // SSEStallError.data.message ("SSE read timed out after 1000ms").
276284 expect ( observed . message ) . toMatch ( / S S E | t i m e d o u t / i)
277285
278- // Stop the loop before the 2s exponential backoff fires a second
279- // attempt (and another 1s stall) and blows the 15s test budget.
280- yield * prompt . cancel ( chat . id )
281- yield * Fiber . await ( fiber )
286+ // Cleanup runs via Effect.ensuring above so failures don't leak a live loop.
282287 } ) ,
283288 { git : true , config : ( url ) => providerCfg ( url , 1_000 ) } ,
284289 ) ,
0 commit comments