fix: keep prior saves visible after interrupted sandbox runs

Report incomplete terminal saves without hiding the latest successful checkpoint, including same-sandbox continuation and replacement failures.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-09-08 16:41:32 -05:00
parent ffe3a32b2d
commit b8d003baff
3 changed files with 48 additions and 11 deletions

View File

@ -228,7 +228,9 @@ Native continuation validates the full control-plane journal with the same
2 MiB without invalidating its identity. Oversized, malformed, foreign-session,
and unverifiable state still fail closed and remain recoverable in quarantine.
The cached-file routes initialize storage once per router after authorization;
every request still checks current owner access.
every request still checks current owner access. If a run ends while its save is
starting or in progress, the inspector reports the interruption and keeps the
previous successful save time visible, including across sandbox replacement.
Passing acceptance does not authorize a merge or mainline release. Both require
the user's explicit sign-off.

View File

@ -40,3 +40,26 @@ it("initializes storage only after access succeeds and reuses it while checking
expect(mocks.access).toHaveBeenCalledTimes(4);
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) => {
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",
};
const interrupted = {
folderRun: { runId: "interrupted-run", manifest: { agentId: "agent", sandboxKey },
state: "starting", lastSavedAt: null, error: null, refreshRequested: false },
status: "failed",
};
const query = { from: () => query, innerJoin: () => query, where: () => query,
orderBy: () => query, limit: async () => [interrupted, oldSave] };
const app = express();
app.use("/api", workFolderRoutes({ select: () => query } as unknown as Db));
const response = await request(app).get("/api/companies/11111111-1111-4111-8111-111111111111/work-folders/task/22222222-2222-4222-8222-222222222222/sync").expect(200);
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" }),
]);
});

View File

@ -86,19 +86,31 @@ export function workFolderRoutes(db: Db, provider?: StorageProvider) {
.where(and(eq(workFolderRuns.companyId, owner.companyId),
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)
.sort((a, b) => b.folderRun.lastSavedAt!.getTime() - a.folderRun.lastSavedAt!.getTime())[0];
const projectStatus = ({ folderRun: row, status }: typeof rows[number]) => {
const active = isActive(status);
const interrupted = !active && (row.state === "starting" || row.state === "saving");
return { runId: row.runId, agentId: row.manifest.agentId, state: interrupted ? "failed" : row.state,
lastSavedAt: row.lastSavedAt, error: interrupted ? row.error ?? "Run ended before its final file save completed." : row.error,
refreshRequested: row.refreshRequested, active };
};
const leases = new Set<string>();
let includedCompletedSave = false;
res.json(rows.flatMap(({ folderRun: row, status }) => {
const statuses = rows.flatMap((entry) => {
const row = entry.folderRun;
if (leases.has(row.manifest.sandboxKey)) return [];
leases.add(row.manifest.sandboxKey);
const active = status === "running" || status === "queued";
if (!active && row.state !== "failed") {
if (includedCompletedSave) return [];
includedCompletedSave = true;
}
return [{ runId: row.runId, agentId: row.manifest.agentId, state: row.state, lastSavedAt: row.lastSavedAt, error: row.error,
refreshRequested: row.refreshRequested, active }];
}));
if (!isActive(entry.status) && row.state === "saved" && row.runId !== latestCompletedSave?.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));
}
res.json(statuses);
});
router.post(`${base}/refresh`, async (req, res) => {
const owner = ownerSchema.parse(req.params);