@@ -381,8 +381,16 @@ export const runTask = async ({ run, task, publish, logger, jobToken }: RunTaskO
381381 // Wait for taskMetricsId to be set by the TaskStarted handler.
382382 // This prevents a race condition where these events arrive before
383383 // the TaskStarted handler finishes its async database operations.
384+ // Note: taskMetricsReady is also resolved on disconnect to prevent deadlock.
384385 await taskMetricsReady
385386
387+ // Guard: taskMetricsReady may have been resolved due to disconnect
388+ // without taskMetricsId being set. Skip metrics update in this case.
389+ if ( ! taskMetricsId ) {
390+ logger . info ( `skipping metrics update: taskMetricsId not set (event: ${ eventName } )` )
391+ return
392+ }
393+
386394 const duration = Date . now ( ) - taskStartedAt
387395
388396 const { totalCost, totalTokensIn, totalTokensOut, contextTokens, totalCacheWrites, totalCacheReads } =
@@ -409,7 +417,7 @@ export const runTask = async ({ run, task, publish, logger, jobToken }: RunTaskO
409417 }
410418 }
411419
412- await updateTaskMetrics ( taskMetricsId ! , {
420+ await updateTaskMetrics ( taskMetricsId , {
413421 cost : totalCost ,
414422 tokensIn : totalTokensIn ,
415423 tokensOut : totalTokensOut ,
@@ -433,6 +441,10 @@ export const runTask = async ({ run, task, publish, logger, jobToken }: RunTaskO
433441 client . on ( IpcMessageType . Disconnect , async ( ) => {
434442 logger . info ( `disconnected from IPC socket -> ${ ipcSocketPath } ` )
435443 isClientDisconnected = true
444+ // Resolve taskMetricsReady to unblock any handlers waiting on it.
445+ // This prevents deadlock if TaskStarted never fired or threw before resolving.
446+ // The handlers check for taskMetricsId being set before proceeding.
447+ resolveTaskMetricsReady ( )
436448 } )
437449
438450 client . sendCommand ( {
0 commit comments