From f8a90f44027de754bf62369df7bbf13971ba7706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 23 Apr 2026 18:58:06 -0400 Subject: [PATCH] fix(checkpoint): fail closed on pause queries --- src/main/checkpoint-engine.ts | 2 +- src/main/thread-integration/index.ts | 8 +++--- .../integration/thread-wait-heartbeat.spec.ts | 28 ++++++++++++++++++- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/main/checkpoint-engine.ts b/src/main/checkpoint-engine.ts index e49c9277..d90d645f 100644 --- a/src/main/checkpoint-engine.ts +++ b/src/main/checkpoint-engine.ts @@ -157,7 +157,7 @@ export function createCheckpointEngine(deps: CheckpointDeps) { return db.core.approvals.hasPendingReviewApprovalForAgent(companyId, agentId); } catch (err) { logger.error(`[checkpoint] shouldPauseHeartbeat failed: ${err instanceof Error ? err.message : String(err)}`); - return false; + return true; } }, diff --git a/src/main/thread-integration/index.ts b/src/main/thread-integration/index.ts index 34a5d3a8..d685996f 100644 --- a/src/main/thread-integration/index.ts +++ b/src/main/thread-integration/index.ts @@ -324,8 +324,8 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh const waitStates = db.core.threads.listThreadWaitStates(companyId); return waitStates.length > 0; } catch (err) { - logger.warn(`[thread-integration] hasUnresolvedWaitStates query failed for company ${companyId}:`, err instanceof Error ? err.message : String(err)); - return false; + logger.warn(`[thread-integration] hasUnresolvedWaitStates query failed for company ${companyId}; treating as blocked:`, err instanceof Error ? err.message : String(err)); + return true; } }, @@ -339,8 +339,8 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh try { return db.core.threads.hasUnresolvedWaitStateForAgent(companyId, agentId); } catch (err) { - logger.warn(`[thread-integration] hasUnresolvedWaitStatesForAgent query failed (company=${companyId} agent=${agentId}):`, err instanceof Error ? err.message : String(err)); - return false; + logger.warn(`[thread-integration] hasUnresolvedWaitStatesForAgent query failed (company=${companyId} agent=${agentId}); treating as blocked:`, err instanceof Error ? err.message : String(err)); + return true; } }, diff --git a/tests/integration/thread-wait-heartbeat.spec.ts b/tests/integration/thread-wait-heartbeat.spec.ts index 2b4b65ab..ebb4e910 100644 --- a/tests/integration/thread-wait-heartbeat.spec.ts +++ b/tests/integration/thread-wait-heartbeat.spec.ts @@ -1,7 +1,7 @@ import { mkdtemp, rm } from "node:fs/promises"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { AppDatabase } from "../../src/main/database"; import { createThreadIntegration } from "../../src/main/thread-integration"; import { createCheckpointEngine } from "../../src/main/checkpoint-engine"; @@ -250,4 +250,30 @@ describe("thread wait + heartbeat integration", () => { db.close(); }); + + it("keeps co-founder heartbeat paused when wait-state lookup fails", async () => { + const profileDir = await createTempDir("agentcompany-wait-query-fail-"); + const db = new AppDatabase(profileDir); + db.init(); + + const { companyId, engineerId } = createScaffold(db); + const threadIntegration = createThreadIntegration({ + db, + logger: createNoopLogger(), + }); + const checkpointEngine = createCheckpointEngine({ + db, + logger: createNoopLogger(), + threadIntegration, + publishDomainChanged: () => {}, + notify: () => {}, + }); + vi.spyOn(db.core.threads, "hasUnresolvedWaitStateForAgent").mockImplementation(() => { + throw new Error("wait-state query failed"); + }); + + expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(true); + + db.close(); + }); });