From 3b588283b19f48c7a98c7bdb10e250ea16dd454e Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sun, 8 Mar 2026 02:13:49 +0900 Subject: [PATCH] fix(background-agent): skip terminal tasks during stale pruning Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus --- .../background-agent/task-poller.test.ts | 34 +++++++++++++++++++ src/features/background-agent/task-poller.ts | 9 +++++ 2 files changed, 43 insertions(+) diff --git a/src/features/background-agent/task-poller.test.ts b/src/features/background-agent/task-poller.test.ts index d411cb240..12d733803 100644 --- a/src/features/background-agent/task-poller.test.ts +++ b/src/features/background-agent/task-poller.test.ts @@ -422,4 +422,38 @@ describe("pruneStaleTasksAndNotifications", () => { //#then expect(pruned).toContain("old-task") }) + + it("should skip terminal tasks even when they exceeded TTL", () => { + //#given + const tasks = new Map() + const oldStartedAt = new Date(Date.now() - 31 * 60 * 1000) + const terminalStatuses: BackgroundTask["status"][] = ["completed", "error", "cancelled", "interrupt"] + + for (const status of terminalStatuses) { + tasks.set(status, { + id: status, + parentSessionID: "parent", + parentMessageID: "msg", + description: status, + prompt: status, + agent: "explore", + status, + startedAt: oldStartedAt, + completedAt: new Date(), + }) + } + + const pruned: string[] = [] + + //#when + pruneStaleTasksAndNotifications({ + tasks, + notifications: new Map(), + onTaskPruned: (taskId) => pruned.push(taskId), + }) + + //#then + expect(pruned).toEqual([]) + expect(Array.from(tasks.keys())).toEqual(terminalStatuses) + }) }) diff --git a/src/features/background-agent/task-poller.ts b/src/features/background-agent/task-poller.ts index eca83bc66..37bebd798 100644 --- a/src/features/background-agent/task-poller.ts +++ b/src/features/background-agent/task-poller.ts @@ -12,6 +12,13 @@ import { TASK_TTL_MS, } from "./constants" +const TERMINAL_TASK_STATUSES = new Set([ + "completed", + "error", + "cancelled", + "interrupt", +]) + export function pruneStaleTasksAndNotifications(args: { tasks: Map notifications: Map @@ -21,6 +28,8 @@ export function pruneStaleTasksAndNotifications(args: { const now = Date.now() for (const [taskId, task] of tasks.entries()) { + if (TERMINAL_TASK_STATUSES.has(task.status)) continue + const timestamp = task.status === "pending" ? task.queuedAt?.getTime() : task.startedAt?.getTime()