From 7911bb781ae24acf92747f65b104e5dd777dd33e Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 10 Jun 2026 13:28:30 +0000 Subject: [PATCH] Release checkout lock for process loss retries --- server/src/__tests__/heartbeat-process-recovery.test.ts | 6 ++++-- server/src/services/heartbeat.ts | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/server/src/__tests__/heartbeat-process-recovery.test.ts b/server/src/__tests__/heartbeat-process-recovery.test.ts index 58c86b967c9..2ecbbb14abc 100644 --- a/server/src/__tests__/heartbeat-process-recovery.test.ts +++ b/server/src/__tests__/heartbeat-process-recovery.test.ts @@ -955,7 +955,8 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { .where(eq(issues.id, issueId)) .then((rows) => rows[0] ?? null); expect(issue?.executionRunId).toBe(retryRun?.id ?? null); - expect(issue?.checkoutRunId).toBe(runId); + // Terminal run cleanup releases the checkout lock so future checkout 409s only mean a live owner exists. + expect(issue?.checkoutRunId).toBeNull(); }); it("releases active environment leases when an orphaned run is reaped", async () => { @@ -1201,7 +1202,8 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { const issue = await db.select().from(issues).where(eq(issues.id, issueId)).then((rows) => rows[0] ?? null); expect(issue?.status).toBe("in_progress"); expect(issue?.executionRunId).toBeNull(); - expect(issue?.checkoutRunId).toBe(runId); + // Terminal run cleanup releases the checkout lock even when paused-tree recovery is suppressed. + expect(issue?.checkoutRunId).toBeNull(); const recoveryIssues = await db .select() diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index 9728466d145..a3c52d145a7 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -4695,6 +4695,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {}) await tx .update(issues) .set({ + checkoutRunId: null, executionRunId: retryRun.id, executionAgentNameKey: normalizeAgentNameKey(agent.name), executionLockedAt: now,