mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
fix(heartbeat): defer dead-run finalize until retries settle
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
276e0d83c6
commit
44a36400ad
2 files changed
+65
-1
No files matched your search
@@ -1954,6 +1954,70 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => {
|
||||
expect(lease?.releasedAt).toBeTruthy();
|
||||
});
|
||||
|
||||
it("does not synthesize a finalize barrier when the dead run is retried", async () => {
|
||||
const { companyId, runId, issueId } = await seedRunFixture({
|
||||
agentStatus: "idle",
|
||||
processPid: 999_999_999,
|
||||
});
|
||||
const projectId = randomUUID();
|
||||
const executionWorkspaceId = randomUUID();
|
||||
|
||||
await db.insert(projects).values({
|
||||
id: projectId,
|
||||
companyId,
|
||||
name: "Retried dead-run project",
|
||||
status: "in_progress",
|
||||
});
|
||||
await db.insert(executionWorkspaces).values({
|
||||
id: executionWorkspaceId,
|
||||
companyId,
|
||||
projectId,
|
||||
mode: "shared_workspace",
|
||||
strategyType: "local_path",
|
||||
name: "Retried dead-run workspace",
|
||||
status: "active",
|
||||
providerType: "local_fs",
|
||||
});
|
||||
await db
|
||||
.update(issues)
|
||||
.set({
|
||||
projectId,
|
||||
status: "done",
|
||||
executionWorkspaceId,
|
||||
completedAt: new Date("2026-03-19T00:01:00.000Z"),
|
||||
})
|
||||
.where(eq(issues.id, issueId));
|
||||
await db.insert(workspaceOperations).values({
|
||||
companyId,
|
||||
executionWorkspaceId,
|
||||
heartbeatRunId: runId,
|
||||
issueId,
|
||||
phase: "adapter_execute",
|
||||
status: "succeeded",
|
||||
metadata: { qaFixture: "retried-dead-run-finalize" },
|
||||
startedAt: new Date("2026-03-19T00:00:30.000Z"),
|
||||
finishedAt: new Date("2026-03-19T00:00:45.000Z"),
|
||||
});
|
||||
|
||||
await heartbeatService(db).reapOrphanedRuns();
|
||||
|
||||
const retry = await db
|
||||
.select({ id: heartbeatRuns.id })
|
||||
.from(heartbeatRuns)
|
||||
.where(eq(heartbeatRuns.retryOfRunId, runId))
|
||||
.then((rows) => rows[0] ?? null);
|
||||
expect(retry?.id).toBeTruthy();
|
||||
|
||||
const finalizeRows = await db
|
||||
.select({ id: workspaceOperations.id })
|
||||
.from(workspaceOperations)
|
||||
.where(and(
|
||||
eq(workspaceOperations.heartbeatRunId, runId),
|
||||
eq(workspaceOperations.phase, "workspace_finalize"),
|
||||
));
|
||||
expect(finalizeRows).toEqual([]);
|
||||
});
|
||||
|
||||
it("synthesizes one finalize barrier for a dead done blocker and wakes its dependent", async () => {
|
||||
const { companyId, agentId, runId, issueId: blockerIssueId } = await seedRunFixture({
|
||||
agentStatus: "idle",
|
||||
|
||||
@@ -12439,7 +12439,6 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
|
||||
if (!finalizedRun) finalizedRun = await getRun(run.id);
|
||||
if (!finalizedRun) continue;
|
||||
finalizedRun = await classifyAndPersistRunLiveness(finalizedRun, parseObject(finalizedRun.resultJson)) ?? finalizedRun;
|
||||
await reconcileSyntheticWorkspaceFinalizeForDeadRun(finalizedRun);
|
||||
await releaseEnvironmentLeasesForRun({
|
||||
runId: finalizedRun.id,
|
||||
companyId: finalizedRun.companyId,
|
||||
@@ -12460,6 +12459,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
|
||||
}
|
||||
|
||||
if (!retriedRun) {
|
||||
await reconcileSyntheticWorkspaceFinalizeForDeadRun(finalizedRun);
|
||||
await releaseIssueExecutionAndPromote(finalizedRun);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user