diff --git a/server/src/__tests__/heartbeat-worktree-suppression.test.ts b/server/src/__tests__/heartbeat-worktree-suppression.test.ts index dc2ed876e08..9e12c5f5c40 100644 --- a/server/src/__tests__/heartbeat-worktree-suppression.test.ts +++ b/server/src/__tests__/heartbeat-worktree-suppression.test.ts @@ -170,6 +170,50 @@ describeEmbeddedPostgres("heartbeat worktree suppression", () => { }); }); + it("counts quarantined runs only for the active seed epoch", async () => { + const { companyId, agentId, issueId } = await insertAgentAndIssue(); + const settings = instanceSettingsService(db, { + runtimeEnv: { + PAPERCLIP_IN_WORKTREE: "true", + PAPERCLIP_INSTANCE_ID: "test-worktree", + }, + }); + const experimental = await settings.getExperimental(); + const activeSeedEpoch = experimental.worktreeRunExecutionSeedEpoch; + expect(activeSeedEpoch).not.toBeNull(); + + await db.insert(heartbeatRuns).values([ + { + id: randomUUID(), + companyId, + agentId, + seedEpoch: activeSeedEpoch, + invocationSource: "assignment", + triggerDetail: "system", + status: "cancelled", + errorCode: "worktree_seed_quarantine", + responsibleUserId: "responsible-user", + contextSnapshot: { issueId }, + }, + { + id: randomUUID(), + companyId, + agentId, + seedEpoch: randomUUID(), + invocationSource: "assignment", + triggerDetail: "system", + status: "cancelled", + errorCode: "worktree_seed_quarantine", + responsibleUserId: "responsible-user", + contextSnapshot: { issueId }, + }, + ]); + + await expect(settings.getWorktreeRunEngineStatus()).resolves.toMatchObject({ + quarantinedRunCount: 1, + }); + }); + async function waitForCompletedRun(runId: string, agentId: string) { let latestStatus: string | null = null; let latestLastRunId: string | null = null; diff --git a/server/src/services/instance-settings.ts b/server/src/services/instance-settings.ts index b13d934c533..f97adcf3a2e 100644 --- a/server/src/services/instance-settings.ts +++ b/server/src/services/instance-settings.ts @@ -503,11 +503,16 @@ export function instanceSettingsService(db: Db, options: InstanceSettingsService // Only quarantined-run rows survive a seed (P1 deletes the wakeups/monitors // it clears), so this count is the durable evidence of what was neutralized. let quarantinedRunCount = 0; - if (inWorktree) { + if (inWorktree && experimental.worktreeRunExecutionSeedEpoch) { const [row] = await db .select({ count: sql`count(*)::int` }) .from(heartbeatRuns) - .where(eq(heartbeatRuns.errorCode, WORKTREE_SEED_QUARANTINE_ERROR_CODE)); + .where( + and( + eq(heartbeatRuns.errorCode, WORKTREE_SEED_QUARANTINE_ERROR_CODE), + eq(heartbeatRuns.seedEpoch, experimental.worktreeRunExecutionSeedEpoch), + ), + ); quarantinedRunCount = row?.count ?? 0; } return {