fix: retain periodic checkpoint timestamps after failed final saves

A failed final flush does not erase an earlier successful periodic checkpoint. Cover interrupted continuations and replacement sandboxes for both saved and failed prior runs.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-09-08 16:42:20 -05:00
parent b8d003baff
commit 22d61006b9
2 changed files with 12 additions and 9 deletions

View File

@ -41,11 +41,14 @@ it("initializes storage only after access succeeds and reuses it while checking
expect(mocks.list).toHaveBeenCalledTimes(2);
});
it.each(["same-sandbox", "replacement-sandbox"])("keeps the last successful save visible after an interrupted run on %s", async (sandboxKey) => {
it.each([
["same-sandbox", "saved"], ["replacement-sandbox", "saved"],
["same-sandbox", "failed"], ["replacement-sandbox", "failed"],
])("keeps the last successful checkpoint visible after an interrupted run on %s (prior state %s)", async (sandboxKey, priorState) => {
const oldSave = {
folderRun: { runId: "saved-run", manifest: { agentId: "agent", sandboxKey: "same-sandbox" },
state: "saved", lastSavedAt: new Date("2026-09-08T12:00:00Z"), error: null, refreshRequested: false },
status: "succeeded",
state: priorState, lastSavedAt: new Date("2026-09-08T12:00:00Z"), error: null, refreshRequested: false },
status: priorState === "failed" ? "failed" : "succeeded",
};
const interrupted = {
folderRun: { runId: "interrupted-run", manifest: { agentId: "agent", sandboxKey },
@ -60,6 +63,6 @@ it.each(["same-sandbox", "replacement-sandbox"])("keeps the last successful save
expect(response.body).toEqual([
expect.objectContaining({ runId: "interrupted-run", state: "failed", active: false,
error: "Run ended before its final file save completed.", lastSavedAt: null }),
expect.objectContaining({ runId: "saved-run", state: "saved", lastSavedAt: "2026-09-08T12:00:00.000Z" }),
expect.objectContaining({ runId: "saved-run", state: priorState, lastSavedAt: "2026-09-08T12:00:00.000Z" }),
]);
});

View File

@ -87,8 +87,8 @@ export function workFolderRoutes(db: Db, provider?: StorageProvider) {
sql`${workFolderRuns.manifest}->'folders'->>${owner.scope} = ${folder.id}`))
.orderBy(desc(workFolderRuns.updatedAt)).limit(100);
const isActive = (status: string) => status === "running" || status === "queued";
const latestCompletedSave = rows.filter(({ folderRun, status }) =>
!isActive(status) && folderRun.state === "saved" && folderRun.lastSavedAt !== null)
const latestCheckpoint = rows.filter(({ folderRun, status }) =>
!isActive(status) && folderRun.lastSavedAt !== null)
.sort((a, b) => b.folderRun.lastSavedAt!.getTime() - a.folderRun.lastSavedAt!.getTime())[0];
const projectStatus = ({ folderRun: row, status }: typeof rows[number]) => {
const active = isActive(status);
@ -102,13 +102,13 @@ export function workFolderRoutes(db: Db, provider?: StorageProvider) {
const row = entry.folderRun;
if (leases.has(row.manifest.sandboxKey)) return [];
leases.add(row.manifest.sandboxKey);
if (!isActive(entry.status) && row.state === "saved" && row.runId !== latestCompletedSave?.folderRun.runId) return [];
if (!isActive(entry.status) && row.state === "saved" && row.runId !== latestCheckpoint?.folderRun.runId) return [];
return [projectStatus(entry)];
});
// A failed replacement or later run must not erase the last successful
// checkpoint, including when both runs share the same physical sandbox.
if (latestCompletedSave && !statuses.some((status) => status.runId === latestCompletedSave.folderRun.runId)) {
statuses.push(projectStatus(latestCompletedSave));
if (latestCheckpoint && !statuses.some((status) => status.runId === latestCheckpoint.folderRun.runId)) {
statuses.push(projectStatus(latestCheckpoint));
}
res.json(statuses);
});