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.
This commit is contained in:
ZacharyZhang-NY committed 2026-05-14 14:34:16 -04:00
1 parent 5014ab9651
commit 2b4b8d987e
1 file changed
+18 -6
+18 -6
View File
@@ -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}`);
}