From 8c5f9b8082bff0cec69f254a4eac8ea50b2c660c Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 11 Mar 2026 18:20:13 +0900 Subject: [PATCH] fix(background-agent): skip terminal tasks during stale pruning Prevent TTL pruning from deleting terminal tasks before delayed notification cleanup runs. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../background-agent/task-poller.test.ts | 37 ++++++++++++++++++- src/features/background-agent/task-poller.ts | 9 +++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/features/background-agent/task-poller.test.ts b/src/features/background-agent/task-poller.test.ts index 521a85904..6da3ffb0e 100644 --- a/src/features/background-agent/task-poller.test.ts +++ b/src/features/background-agent/task-poller.test.ts @@ -1,4 +1,5 @@ -import { describe, it, expect, mock } from "bun:test" +declare const require: (name: string) => any +const { describe, it, expect, mock } = require("bun:test") import { checkAndInterruptStaleTasks, pruneStaleTasksAndNotifications } from "./task-poller" import type { BackgroundTask } from "./types" @@ -447,4 +448,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 c27cd4874..bc4f049a4 100644 --- a/src/features/background-agent/task-poller.ts +++ b/src/features/background-agent/task-poller.ts @@ -13,6 +13,13 @@ import { } from "./constants" import { removeTaskToastTracking } from "./remove-task-toast-tracking" +const TERMINAL_TASK_STATUSES = new Set([ + "completed", + "error", + "cancelled", + "interrupt", +]) + export function pruneStaleTasksAndNotifications(args: { tasks: Map notifications: Map @@ -22,6 +29,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()