From 2b4b8d987e260bc83f1dcb4072baa209c7aa7672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 14 May 2026 14:34:16 -0400 Subject: [PATCH] fix(worker): always ack run completion to avoid leaking the active map handleRunFinished and releaseCompletedRunWorkspaceLock both run between receiving run-finished and posting run-completion-ack. If either throws, the ack is skipped and the worker keeps the agent in its `active` map forever; every subsequent wake then queues behind an `isAgentBusy` check that never clears, and manual wakes from the UI silently fail to start. Wrap the post-finalize work in try/catch so the ack is posted unconditionally and the worker state machine stays consistent. --- src/main/worker/entry/message-handler.ts | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/main/worker/entry/message-handler.ts b/src/main/worker/entry/message-handler.ts index 258ecf89..5c851a3a 100644 --- a/src/main/worker/entry/message-handler.ts +++ b/src/main/worker/entry/message-handler.ts @@ -60,14 +60,26 @@ export async function handleWorkerMessage( deps.getRuntimeBridge?.()?.releaseRunResources(event.runId) ?? { ok: true }; return runtimeReleaseResult; }; + // The worker's per-run state machine releases `active[runId]` only + // after receiving run-completion-ack. Any uncaught throw between + // run-finished and posting the ack permanently leaks the agent — + // every subsequent wake then enqueues behind a queue whose + // `isAgentBusy` check refuses to start it. The ack must therefore + // be posted unconditionally. try { - completion = handleRunFinished(event, deps, context, { - beforePostCompletionAutomation: releaseRuntimeResources, - }); - } finally { - releaseRuntimeResources(); + try { + completion = handleRunFinished(event, deps, context, { + beforePostCompletionAutomation: releaseRuntimeResources, + }); + } finally { + releaseRuntimeResources(); + } + releaseCompletedRunWorkspaceLock(completion, deps); + } catch (error) { + deps.logger.error( + `[worker] run-finished post-handling failed for run ${event.runId}: ${error instanceof Error ? error.message : String(error)}`, + ); } - releaseCompletedRunWorkspaceLock(completion, deps); if (!worker) { throw new Error(`run-completion-ack worker missing for run ${event.runId}`); }