fix(worker): claim workspace lock in main before enqueue (D-02)

The lock was previously claimed when the worker reported `running`, which
is after `agent.prompt` had already started streaming. Two concurrent
runs targeting the same workspace could both pass the main-side checks,
both get enqueued, and both read half-written state before one of them
lost the lock race and was cancelled mid-flight.

Move the `claimWorkspaceLock` call into `dispatchRunToWorker` so main
coordinates exclusivity authoritatively. If the lock is unavailable,
the run is finished as failed and never enqueued. The worker-side
lease-renewal path is preserved since it is a lease, not a lock.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-16 21:49:30 -04:00
1 parent f86b3ddbac
commit ff0c01fe87
2 files changed
+27 -4

No files matched your search

+24
View File
@@ -86,6 +86,30 @@ export function createRunDispatch(deps: OrchestrationDependencies) {
throw new Error(connectorIssue);
}
// D-02: Claim the workspace lock synchronously before enqueuing the run.
// Previously the worker claimed the lock only after it emitted `running`,
// so two runs could race between enqueue and startup and both read the
// same workspace mid-write. Main is the authoritative coordinator.
if (
params.workspaceMode === "exclusive" &&
params.workspace &&
!db.claimWorkspaceLock(params.workspace.id, params.runId)
) {
db.finishRun({
runId: params.runId,
status: "failed",
summary: null,
errorMessage: `Workspace ${params.workspace.id} is locked by another run.`,
exitCode: null,
signal: null,
model: null,
sessionDisplayId: null,
costUsd: null,
unattributedCost: true,
});
return;
}
const payload = buildRunPayload(params, {
db,
logger,