fix: clean up attachments for orphaned subagent runs

This commit is contained in:
Tak Hoffman 2026-03-24 10:04:50 -05:00
parent 075ece3dac
commit df2f900677
No known key found for this signature in database
2 changed files with 44 additions and 0 deletions

View File

@ -499,6 +499,45 @@ describe("subagent registry persistence", () => {
expect(listSubagentRunsForRequester("agent:main:main")).toHaveLength(0);
});
it("removes attachments when pruning orphaned restored runs", async () => {
const persisted = createPersistedEndedRun({
runId: "run-orphan-attachments",
childSessionKey: "agent:main:subagent:ghost-attachments",
task: "orphan attachments",
cleanup: "delete",
});
const registryPath = await writePersistedRegistry(persisted, {
seedChildSessions: false,
});
if (!tempStateDir) {
throw new Error("tempStateDir not initialized");
}
const attachmentsRootDir = path.join(tempStateDir, "attachments");
const attachmentsDir = path.join(attachmentsRootDir, "ghost");
await fs.mkdir(attachmentsDir, { recursive: true });
await fs.writeFile(path.join(attachmentsDir, "artifact.txt"), "artifact", "utf8");
const parsed = JSON.parse(await fs.readFile(registryPath, "utf8")) as {
runs?: Record<string, Record<string, unknown>>;
};
if (!parsed.runs?.["run-orphan-attachments"]) {
throw new Error("expected orphaned run in persisted registry");
}
parsed.runs["run-orphan-attachments"] = {
...parsed.runs["run-orphan-attachments"],
attachmentsRootDir,
attachmentsDir,
};
await fs.writeFile(registryPath, `${JSON.stringify(parsed)}\n`, "utf8");
await restartRegistryAndFlush();
await expect(fs.access(attachmentsDir)).rejects.toMatchObject({ code: "ENOENT" });
const after = JSON.parse(await fs.readFile(registryPath, "utf8")) as {
runs?: Record<string, unknown>;
};
expect(after.runs?.["run-orphan-attachments"]).toBeUndefined();
});
it("prefers active runs and can resolve them from persisted registry snapshots", async () => {
const childSessionKey = "agent:main:subagent:disk-active";
await writePersistedRegistry(

View File

@ -287,6 +287,11 @@ function reconcileOrphanedRun(params: {
params.entry.cleanupCompletedAt = now;
changed = true;
}
const shouldDeleteAttachments =
params.entry.cleanup === "delete" || !params.entry.retainAttachmentsOnKeep;
if (shouldDeleteAttachments) {
void safeRemoveAttachmentsDir(params.entry);
}
const removed = subagentRuns.delete(params.runId);
resumedRuns.delete(params.runId);
if (!removed && !changed) {