fix: bound runtime status cleanup to active writes

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-30 16:17:13 -05:00
1 parent 76fde891cc
commit 03ef5469a1
2 files changed
+54 -1

No files matched your search

@@ -1,4 +1,4 @@
import { afterEach, describe, expect, it } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
clearAllHeartbeatRunRuntimeStatuses,
clearHeartbeatRunRuntimeStatus,
@@ -12,6 +12,7 @@ import {
describe("heartbeat run runtime status store", () => {
afterEach(() => {
clearAllHeartbeatRunRuntimeStatuses();
vi.useRealTimers();
});
it("stores scoped ephemeral status and expires stale entries", () => {
@@ -166,6 +167,43 @@ describe("heartbeat run runtime status store", () => {
});
});
it("cleans unread abandoned statuses on later output without polling or expiring fresh context", () => {
vi.useFakeTimers({ toFake: ["Date"] });
const start = new Date("2026-10-30T00:00:00Z");
vi.setSystemTime(start);
const write = (runId: string) => setHeartbeatRunRuntimeStatus({
companyId: "company-1", issueId: null, agentId: "agent-1", runId,
phase: "run_activity", message: "Using Bash",
});
write("abandoned");
write("active");
vi.setSystemTime(start.getTime() + 60_000);
touchHeartbeatRunRuntimeStatus({
companyId: "company-1", issueId: null, agentId: "agent-1", runId: "active",
});
vi.setSystemTime(start.getTime() + 90_000);
write("boundary");
expect(getHeartbeatRunRuntimeStatus("abandoned")).toMatchObject({ message: "Using Bash" });
expect(clearHeartbeatRunRuntimeStatus("boundary")).toBe(true);
vi.setSystemTime(start.getTime() + 90_001);
touchHeartbeatRunRuntimeStatus({
companyId: "company-1", issueId: null, agentId: "agent-1", runId: "active",
});
expect(clearHeartbeatRunRuntimeStatus("abandoned")).toBe(false);
expect(getHeartbeatRunRuntimeStatus("active")).toMatchObject({ message: "Using Bash" });
setHeartbeatRunRuntimeStatus({
companyId: "company-1", issueId: null, agentId: "agent-1", runId: "old-output",
phase: "run_activity", message: "Delayed output", updatedAt: start,
});
vi.setSystemTime(start.getTime() + 90_002);
write("next-output");
expect(clearHeartbeatRunRuntimeStatus("old-output")).toBe(true);
vi.setSystemTime(start.getTime() + 180_002);
write("new");
expect(clearHeartbeatRunRuntimeStatus("active")).toBe(false);
expect(getHeartbeatRunRuntimeStatus("new")).toMatchObject({ message: "Using Bash" });
});
it("sweeps expired statuses without touching fresh entries", () => {
setHeartbeatRunRuntimeStatus({
companyId: "company-1",
@@ -20,6 +20,18 @@ export interface HeartbeatRunRuntimeStatus {
}
const runtimeStatusesByRunId = new Map<string, HeartbeatRunRuntimeStatus>();
let nextWriteCleanupAt: number | null = null;
function cleanupExpiredStatusesOnWrite() {
const now = Date.now();
if (nextWriteCleanupAt === null) {
nextWriteCleanupAt = now + HEARTBEAT_RUN_RUNTIME_STATUS_TTL_MS;
return;
}
if (now <= nextWriteCleanupAt) return;
sweepExpiredHeartbeatRunRuntimeStatuses(new Date(now));
nextWriteCleanupAt = now + HEARTBEAT_RUN_RUNTIME_STATUS_TTL_MS;
}
function cloneStatus(status: HeartbeatRunRuntimeStatus): HeartbeatRunRuntimeStatus {
return {
@@ -64,6 +76,7 @@ export function setHeartbeatRunRuntimeStatus(
lastEventAt?: Date | null;
},
): HeartbeatRunRuntimeStatus | null {
cleanupExpiredStatusesOnWrite();
const message = sanitizeHeartbeatRunRuntimeStatusMessage(input.message);
if (!message) {
clearHeartbeatRunRuntimeStatus(input.runId);
@@ -106,6 +119,7 @@ export function touchHeartbeatRunRuntimeStatus(input: {
fallbackPhase?: HeartbeatRunStatusPhase;
fallbackMessage?: string;
}): HeartbeatRunRuntimeStatus | null {
cleanupExpiredStatusesOnWrite();
const at = input.at ?? new Date();
const existing = runtimeStatusesByRunId.get(input.runId);
if (
@@ -167,6 +181,7 @@ export function clearHeartbeatRunRuntimeStatus(runId: string): boolean {
export function clearAllHeartbeatRunRuntimeStatuses(): void {
runtimeStatusesByRunId.clear();
nextWriteCleanupAt = null;
}
export function sweepExpiredHeartbeatRunRuntimeStatuses(