diff --git a/server/src/__tests__/work-folder-route-storage.test.ts b/server/src/__tests__/work-folder-route-storage.test.ts index bac60eb6cc..fd4ed7a296 100644 --- a/server/src/__tests__/work-folder-route-storage.test.ts +++ b/server/src/__tests__/work-folder-route-storage.test.ts @@ -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" }), ]); }); diff --git a/server/src/routes/work-folders.ts b/server/src/routes/work-folders.ts index 57b68c3f9e..5440ffe820 100644 --- a/server/src/routes/work-folders.ts +++ b/server/src/routes/work-folders.ts @@ -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); });