@@ -626,6 +626,20 @@ export async function initBridgeCore(
626626 }
627627 }
628628
629+ async function closeTransportBestEffort (
630+ transportToClose : ReplBridgeTransport ,
631+ reason : string ,
632+ ) : Promise < void > {
633+ try {
634+ await transportToClose . close ( )
635+ } catch ( err ) {
636+ logForDebugging (
637+ `[bridge:repl] Transport close threw ${ reason } : ${ errorMessage ( err ) } ` ,
638+ { level : 'error' } ,
639+ )
640+ }
641+ }
642+
629643 async function doReconnect ( ) : Promise < boolean > {
630644 environmentRecreations ++
631645 // Invalidate any in-flight v2 handshake — the environment is being
@@ -652,7 +666,7 @@ export async function initBridgeCore(
652666 if ( seq > lastTransportSequenceNum ) {
653667 lastTransportSequenceNum = seq
654668 }
655- transport . close ( )
669+ await closeTransportBestEffort ( transport , 'during reconnect' )
656670 transport = null
657671 }
658672 // Transport is gone — wake the poll loop out of its at-capacity
@@ -1055,7 +1069,7 @@ export async function initBridgeCore(
10551069 // forwarding prompts → ~25-min dead window observed in daemon logs.
10561070 // Kill the transport + work state so isAtCapacity()=false; the loop
10571071 // fast-polls and picks up the server's re-dispatched work in seconds.
1058- onHeartbeatFatal : ( err : BridgeFatalError ) => {
1072+ onHeartbeatFatal : async ( err : BridgeFatalError ) => {
10591073 logForDebugging (
10601074 `[bridge:repl] heartbeatWork fatal (status=${ err . status } ) — tearing down work item for fast re-dispatch` ,
10611075 )
@@ -1064,7 +1078,7 @@ export async function initBridgeCore(
10641078 if ( seq > lastTransportSequenceNum ) {
10651079 lastTransportSequenceNum = seq
10661080 }
1067- transport . close ( )
1081+ await closeTransportBestEffort ( transport , 'after heartbeat fatal' )
10681082 transport = null
10691083 }
10701084 flushGate . drop ( )
@@ -1094,7 +1108,7 @@ export async function initBridgeCore(
10941108 }
10951109 return { environmentId, environmentSecret }
10961110 } ,
1097- onWorkReceived : (
1111+ onWorkReceived : async (
10981112 workSessionId : string ,
10991113 ingressToken : string ,
11001114 workId : string ,
@@ -1199,7 +1213,10 @@ export async function initBridgeCore(
11991213 if ( oldSeq > lastTransportSequenceNum ) {
12001214 lastTransportSequenceNum = oldSeq
12011215 }
1202- oldTransport . close ( )
1216+ await closeTransportBestEffort (
1217+ oldTransport ,
1218+ 'while replacing work transport' ,
1219+ )
12031220 }
12041221 // Reset flush state — the old flush (if any) is no longer relevant.
12051222 // Preserve pending messages so they're drained after the new
@@ -1410,13 +1427,16 @@ export async function initBridgeCore(
14101427 sessionId : workSessionId ,
14111428 initialSequenceNum : lastTransportSequenceNum ,
14121429 } ) . then (
1413- t => {
1430+ async t => {
14141431 // Teardown started while registerWorker was in flight. Teardown
14151432 // saw transport === null and skipped close(); installing now
14161433 // would leak CCRClient heartbeat timers and reset
14171434 // teardownStarted via wireTransport's side effects.
14181435 if ( pollController . signal . aborted ) {
1419- t . close ( )
1436+ await closeTransportBestEffort (
1437+ t ,
1438+ 'while discarding aborted CCR v2 transport' ,
1439+ )
14201440 return
14211441 }
14221442 // onWorkReceived may have fired again while registerWorker()
@@ -1429,7 +1449,10 @@ export async function initBridgeCore(
14291449 logForDebugging (
14301450 `[bridge:repl] CCR v2: discarding stale handshake gen=${ thisGen } current=${ v2Generation } ` ,
14311451 )
1432- t . close ( )
1452+ await closeTransportBestEffort (
1453+ t ,
1454+ 'while discarding stale CCR v2 transport' ,
1455+ )
14331456 return
14341457 }
14351458 wireTransport ( t )
@@ -1668,8 +1691,10 @@ export async function initBridgeCore(
16681691 // log their own success/failure internally.
16691692 await Promise . all ( [ stopWorkP , archiveSession ( currentSessionId ) ] )
16701693
1671- teardownTransport ?. close ( )
1672- logForDebugging ( '[bridge:repl] Teardown: transport closed' )
1694+ if ( teardownTransport ) {
1695+ await closeTransportBestEffort ( teardownTransport , 'during teardown' )
1696+ logForDebugging ( '[bridge:repl] Teardown: transport closed' )
1697+ }
16731698
16741699 await api . deregisterEnvironment ( environmentId ) . catch ( ( err : unknown ) => {
16751700 logForDebugging (
@@ -1892,7 +1917,7 @@ async function startWorkPollLoop({
18921917 ingressToken : string ,
18931918 workId : string ,
18941919 useCodeSessions : boolean ,
1895- ) => void
1920+ ) => Promise < void >
18961921 /** Called when the environment has been deleted. Returns new credentials or null. */
18971922 onEnvironmentLost ?: ( ) => Promise < {
18981923 environmentId : string
@@ -1935,7 +1960,7 @@ async function startWorkPollLoop({
19351960 * ~10-minute dead window before recovery). When omitted, falls back to
19361961 * the backoff sleep to avoid a tight poll+heartbeat loop.
19371962 */
1938- onHeartbeatFatal ?: ( err : BridgeFatalError ) => void
1963+ onHeartbeatFatal ?: ( err : BridgeFatalError ) => Promise < void >
19391964} ) : Promise < void > {
19401965 const MAX_ENVIRONMENT_RECREATIONS = 3
19411966
@@ -2063,7 +2088,7 @@ async function startWorkPollLoop({
20632088 // for the server's re-dispatched work item. Without
20642089 // the hook, backoff to avoid tight poll+heartbeat loop.
20652090 if ( onHeartbeatFatal ) {
2066- onHeartbeatFatal ( err )
2091+ await onHeartbeatFatal ( err )
20672092 logForDebugging (
20682093 `[bridge:repl:heartbeat] Fatal (status=${ err . status } ), work state cleared — fast-polling for re-dispatch` ,
20692094 )
@@ -2197,7 +2222,7 @@ async function startWorkPollLoop({
21972222 continue
21982223 }
21992224
2200- onWorkReceived (
2225+ await onWorkReceived (
22012226 workSessionId ,
22022227 secret . session_ingress_token ,
22032228 work . id ,
0 commit comments