From 6fee0c3b4ca82304b0976785162f2fe1b69693f9 Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 9 Sep 2026 01:47:49 -0500 Subject: [PATCH] fix: use the new run deadline while claiming a retained sandbox Co-Authored-By: Paperclip --- server/src/__tests__/environment-runtime.test.ts | 4 ++++ server/src/services/environment-runtime.ts | 4 +++- server/src/services/environments.ts | 7 +++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/server/src/__tests__/environment-runtime.test.ts b/server/src/__tests__/environment-runtime.test.ts index c4b587e2e6..d2466039af 100644 --- a/server/src/__tests__/environment-runtime.test.ts +++ b/server/src/__tests__/environment-runtime.test.ts @@ -5860,6 +5860,7 @@ describeEmbeddedPostgres("environmentRuntimeService", () => { // flight. It must see the new database owner before it can touch the disk. await db.update(heartbeatRuns).set({ status: "succeeded" }).where(eq(heartbeatRuns.id, nextRunId)); await environmentService(db).releaseLease(result.value.lease.id, "released"); + await db.update(environmentLeases).set({ expiresAt: new Date(0) }).where(eq(environmentLeases.id, result.value.lease.id)); const raceIds = [randomUUID(), randomUUID()]; await db.insert(heartbeatRuns).values(raceIds.map((id) => ({ id, companyId: seeded.companyId, agentId: seeded.agentId, status: "running", @@ -5881,6 +5882,9 @@ describeEmbeddedPostgres("environmentRuntimeService", () => { // If acquisition regresses before the RPC, surface that rejection too. await Promise.race([resumeStarted, winner.then(() => undefined)]); try { + const claimed = (await environmentService(db).listLeases(seeded.environment.id)) + .find((lease) => lease.heartbeatRunId === raceIds[0]); + expect(claimed).toMatchObject({ status: "active", expiresAt: null }); const otherServer = environmentRuntimeService(db, { pluginWorkerManager: workerManager }); await expect(otherServer.acquireRunLease({ companyId: seeded.companyId, environment: seeded.environment, issueId: taskId, diff --git a/server/src/services/environment-runtime.ts b/server/src/services/environment-runtime.ts index 9bc92af396..9e6c07e742 100644 --- a/server/src/services/environment-runtime.ts +++ b/server/src/services/environment-runtime.ts @@ -1314,7 +1314,9 @@ function createSandboxEnvironmentDriver( leasePolicy: lease.leasePolicy, provider: lease.provider, providerLeaseId: lease.providerLeaseId, - expiresAt: lease.expiresAt ? new Date(lease.expiresAt) : null, + // The retained provider's old expiry may already be past. The resume RPC + // will attest its current expiry; until then use only this run's deadline. + expiresAt: input.requestedExpiresAt ?? null, metadata: lease.metadata, replacesReusableLeaseId: lease.id, }); diff --git a/server/src/services/environments.ts b/server/src/services/environments.ts index 60312355f6..ae46d86f1b 100644 --- a/server/src/services/environments.ts +++ b/server/src/services/environments.ts @@ -1337,10 +1337,9 @@ export function environmentService(db: Db) { metadata?: Record | null; /** * Atomically retire the previous database ownership record when this - * acquisition reuses the same provider resource for a new run. The - * provider resume happens before this write, so a failed transaction - * leaves the prior retained row recoverable instead of publishing two - * reusable owners for one sandbox. + * acquisition reuses the same provider resource for a new run. Claim it + * before the resume RPC: a failed transaction preserves the prior row, + * and a competing acquisition cannot touch the provider without ownership. */ replacesReusableLeaseId?: string | null; /**