diff --git a/src/agents/subagent-registry.test.ts b/src/agents/subagent-registry.test.ts index 015fce8f296..16e9ee7d88b 100644 --- a/src/agents/subagent-registry.test.ts +++ b/src/agents/subagent-registry.test.ts @@ -1,3 +1,6 @@ +import { promises as fs } from "node:fs"; +import os from "node:os"; +import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const noop = () => {}; @@ -431,4 +434,34 @@ describe("subagent registry seam flow", () => { }); }); }); + + it("removes attachments for killed delete-mode runs", async () => { + const attachmentsRootDir = await fs.mkdtemp( + path.join(os.tmpdir(), "openclaw-kill-attachments-"), + ); + const attachmentsDir = path.join(attachmentsRootDir, "child"); + await fs.mkdir(attachmentsDir, { recursive: true }); + await fs.writeFile(path.join(attachmentsDir, "artifact.txt"), "artifact"); + + mod.registerSubagentRun({ + runId: "run-killed-delete-attachments", + childSessionKey: "agent:main:subagent:killed-delete-attachments", + requesterSessionKey: "agent:main:main", + requesterDisplayKey: "main", + task: "kill and delete attachments", + cleanup: "delete", + attachmentsDir, + attachmentsRootDir, + }); + + const updated = mod.markSubagentRunTerminated({ + runId: "run-killed-delete-attachments", + reason: "manual kill", + }); + + expect(updated).toBe(1); + await vi.waitFor(async () => { + await expect(fs.access(attachmentsDir)).rejects.toMatchObject({ code: "ENOENT" }); + }); + }); }); diff --git a/src/agents/subagent-registry.ts b/src/agents/subagent-registry.ts index f28fbe75f3c..ad6ed9b2e7f 100644 --- a/src/agents/subagent-registry.ts +++ b/src/agents/subagent-registry.ts @@ -1654,6 +1654,10 @@ export function markSubagentRunTerminated(params: { childSessionKey: entry.childSessionKey, }); }); + const shouldDeleteAttachments = entry.cleanup === "delete" || !entry.retainAttachmentsOnKeep; + if (shouldDeleteAttachments) { + void safeRemoveAttachmentsDir(entry); + } completeCleanupBookkeeping({ runId: entry.runId, entry,