diff --git a/server/src/modules/agent-lifecycle/__tests__/lifecycle.test.ts b/server/src/modules/agent-lifecycle/__tests__/lifecycle.test.ts index 404e76d6b30..ab4e53d0839 100644 --- a/server/src/modules/agent-lifecycle/__tests__/lifecycle.test.ts +++ b/server/src/modules/agent-lifecycle/__tests__/lifecycle.test.ts @@ -414,6 +414,21 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu expect((await db.select().from(nativeRunFinalizations).where(eq(nativeRunFinalizations.runId, run.id)))[0]).toMatchObject({ leaseOwner: null, leaseExpiresAt: null }); }); + it("releases an expired native owner whose renewed lease expiry has microsecond precision", async () => { + const agent = await hire(); + const [issue] = await db.insert(issues).values({ companyId, title: "Renewed native run" }).returning(); + const [run] = await db.insert(heartbeatRuns).values({ companyId, agentId: agent.id, nativeIssueId: issue.id, + status: "cancelled", invocationSource: "on_demand", runtimeMode: "native", + startedAt: new Date(), finishedAt: new Date(), processPid: 2_000_000_000 }).returning(); + await db.insert(nativeRunFinalizations).values({ companyId, issueId: issue.id, runId: run.id, + phase: "executing", leaseOwner: "stopped-server", leaseExpiresAt: new Date(0) }); + await db.update(nativeRunFinalizations) + .set({ leaseExpiresAt: sql`'2020-01-01T00:00:00.123456Z'::timestamptz` }) + .where(eq(nativeRunFinalizations.runId, run.id)); + expect(await agentExecutionsHaveStopped(db, [agent.id])).toBe(true); + expect((await db.select().from(nativeRunFinalizations).where(eq(nativeRunFinalizations.runId, run.id)))[0]).toMatchObject({ leaseOwner: null, leaseExpiresAt: null }); + }); + it("does not advance termination while a cancelled process still runs", async () => { const agent = await hire(); const [run] = await db.insert(heartbeatRuns).values({ companyId, agentId: agent.id, diff --git a/server/src/services/agent-execution-stop.ts b/server/src/services/agent-execution-stop.ts index 3fad14e89b1..92e9e5964bb 100644 --- a/server/src/services/agent-execution-stop.ts +++ b/server/src/services/agent-execution-stop.ts @@ -64,7 +64,7 @@ export async function agentExecutionsHaveStopped(db: Db, agentIds: string[]) { const [cleared] = await db.update(nativeRunFinalizations).set({ leaseOwner: null, leaseExpiresAt: null, updatedAt: now }) .where(and(eq(nativeRunFinalizations.runId, coordinator.runId), eq(nativeRunFinalizations.leaseOwner, coordinator.leaseOwner!), - eq(nativeRunFinalizations.leaseExpiresAt, coordinator.leaseExpiresAt!), + sql`date_trunc('milliseconds', ${nativeRunFinalizations.leaseExpiresAt}) = ${coordinator.leaseExpiresAt!.toISOString()}::timestamptz`, eq(nativeRunFinalizations.controllerGeneration, coordinator.controllerGeneration), eq(nativeRunFinalizations.phase, coordinator.phase), isNull(nativeRunFinalizations.resultId))) .returning({ runId: nativeRunFinalizations.runId });