diff --git a/src/tasks/task-registry.maintenance.ts b/src/tasks/task-registry.maintenance.ts index 4bfb4187a55..55f80ab321d 100644 --- a/src/tasks/task-registry.maintenance.ts +++ b/src/tasks/task-registry.maintenance.ts @@ -205,6 +205,16 @@ function yieldToEventLoop(): Promise { return new Promise((resolve) => setImmediate(resolve)); } +function startScheduledSweep() { + if (sweepInProgress) { + return; + } + sweepInProgress = true; + void sweepTaskRegistry().finally(() => { + sweepInProgress = false; + }); +} + export async function runTaskRegistryMaintenance(): Promise { ensureTaskRegistryReady(); const now = Date.now(); @@ -256,32 +266,15 @@ export async function sweepTaskRegistry(): Promise { deferredSweep = null; - if (sweepInProgress) { - return; - } - sweepInProgress = true; - void sweepTaskRegistry().finally(() => { - sweepInProgress = false; - }); + startScheduledSweep(); }, 5_000); + deferredSweep.unref?.(); if (sweeper) { return; } - sweeper = setInterval(() => { - // Prevent overlapping sweeps — if a previous async sweep is still - // running, skip this tick entirely. - if (sweepInProgress) { - return; - } - sweepInProgress = true; - void sweepTaskRegistry().finally(() => { - sweepInProgress = false; - }); - }, TASK_SWEEP_INTERVAL_MS); + sweeper = setInterval(startScheduledSweep, TASK_SWEEP_INTERVAL_MS); sweeper.unref?.(); } diff --git a/src/tasks/task-registry.test.ts b/src/tasks/task-registry.test.ts index f7759020993..6d9a6b14704 100644 --- a/src/tasks/task-registry.test.ts +++ b/src/tasks/task-registry.test.ts @@ -36,6 +36,8 @@ import { previewTaskRegistryMaintenance, reconcileInspectableTasks, runTaskRegistryMaintenance, + startTaskRegistryMaintenance, + stopTaskRegistryMaintenanceForTests, sweepTaskRegistry, } from "./task-registry.maintenance.js"; import { configureTaskRegistryRuntime } from "./task-registry.store.js"; @@ -1119,6 +1121,40 @@ describe("task-registry", () => { }); }); + it("cancels the deferred maintenance sweep during test teardown", async () => { + await withTaskRegistryTempDir(async (root) => { + vi.useFakeTimers(); + process.env.OPENCLAW_STATE_DIR = root; + resetTaskRegistryForTests(); + const now = Date.now(); + + const task = createTaskRecord({ + runtime: "acp", + ownerKey: "agent:main:main", + scopeKind: "session", + childSessionKey: "agent:main:acp:missing", + runId: "run-deferred-maintenance-stop", + task: "Missing child", + status: "running", + deliveryStatus: "pending", + }); + setTaskTimingById({ + taskId: task.taskId, + lastEventAt: now - 10 * 60_000, + }); + + startTaskRegistryMaintenance(); + stopTaskRegistryMaintenanceForTests(); + + await vi.advanceTimersByTimeAsync(5_000); + await flushAsyncWork(); + + expect(getTaskById(task.taskId)).toMatchObject({ + status: "running", + }); + }); + }); + it("summarizes inspectable task audit findings", async () => { await withTaskRegistryTempDir(async (root) => { process.env.OPENCLAW_STATE_DIR = root;