diff --git a/server/src/__tests__/heartbeat-dependency-scheduling.test.ts b/server/src/__tests__/heartbeat-dependency-scheduling.test.ts index ec5430e2a0c..3c10fc40de7 100644 --- a/server/src/__tests__/heartbeat-dependency-scheduling.test.ts +++ b/server/src/__tests__/heartbeat-dependency-scheduling.test.ts @@ -280,6 +280,23 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = unresolvedBlockerIssueIds: [blockerId], }); + let finishReadyRun!: () => void; + const readyRunCanFinish = new Promise((resolve) => { + finishReadyRun = resolve; + }); + mockAdapterExecute.mockImplementationOnce(async () => { + await readyRunCanFinish; + return { + exitCode: 0, + signal: null, + timedOut: false, + errorMessage: null, + summary: "Ready dependency scheduling run complete.", + provider: "test", + model: "test-model", + }; + }); + const readyWake = await heartbeat.wakeup(agentId, { source: "assignment", triggerDetail: "system", @@ -288,6 +305,15 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = contextSnapshot: { issueId: readyIssueId, wakeReason: "issue_assigned" }, }); expect(readyWake).not.toBeNull(); + await db.insert(issueComments).values({ + companyId, + issueId: readyIssueId, + authorAgentId: agentId, + authorType: "agent", + createdByRunId: readyWake!.id, + body: "Ready dependency scheduling run complete.", + }); + finishReadyRun(); await waitForCondition(async () => { const run = await db @@ -354,6 +380,14 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = expect(promotedBlockedRun?.status).toBe("succeeded"); expect(blockedWakeRequestCount).toBeGreaterThanOrEqual(2); + + const noActiveRuns = await waitForCondition(async () => { + const rows = await db + .select({ status: heartbeatRuns.status }) + .from(heartbeatRuns); + return rows.every((run) => run.status !== "queued" && run.status !== "running"); + }, 10_000); + expect(noActiveRuns).toBe(true); }); it("honors maxConcurrentRuns 1 by leaving a second assignment wake queued", async () => { @@ -429,6 +463,14 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = contextSnapshot: { issueId: firstIssueId, wakeReason: "issue_assigned" }, }); expect(firstWake).not.toBeNull(); + await db.insert(issueComments).values({ + companyId, + issueId: firstIssueId, + authorAgentId: agentId, + authorType: "agent", + createdByRunId: firstWake!.id, + body: "First assignment run completed.", + }); const firstRunStarted = await waitForCondition(async () => { const run = await db @@ -439,7 +481,7 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = return run?.status === "running"; }); expect(firstRunStarted).toBe(true); - const firstAdapterStarted = await waitForCondition(async () => mockAdapterExecute.mock.calls.length === 1); + const firstAdapterStarted = await waitForCondition(async () => mockAdapterExecute.mock.calls.length === 1, 30_000); expect(firstAdapterStarted).toBe(true); const secondWake = await heartbeat.wakeup(agentId, { @@ -450,6 +492,14 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = contextSnapshot: { issueId: secondIssueId, wakeReason: "issue_assigned" }, }); expect(secondWake).not.toBeNull(); + await db.insert(issueComments).values({ + companyId, + issueId: secondIssueId, + authorAgentId: agentId, + authorType: "agent", + createdByRunId: secondWake!.id, + body: "Second assignment run completed.", + }); const secondRunWhileFirstRunning = await db .select({ status: heartbeatRuns.status }) @@ -470,11 +520,11 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = return run?.status === "succeeded"; }); expect(secondRunSucceeded).toBe(true); - expect(mockAdapterExecute).toHaveBeenCalledTimes(2); + expect(mockAdapterExecute).toHaveBeenCalledTimes(3); } finally { finishFirstRun(); } - }); + }, 40_000); it("cancels stale queued runs when issue blockers are still unresolved", async () => { const companyId = randomUUID(); @@ -598,6 +648,14 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = .update(agentWakeupRequests) .set({ runId: readyRunId }) .where(eq(agentWakeupRequests.id, readyWakeupRequestId)); + await db.insert(issueComments).values({ + companyId, + issueId: readyIssueId, + authorAgentId: agentId, + authorType: "agent", + createdByRunId: readyRunId, + body: "Ready queued run completed.", + }); await db .update(issues) .set({ @@ -665,7 +723,7 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = executionLockedAt: null, }); expect(readyRun?.status).toBe("succeeded"); - expect(mockAdapterExecute).toHaveBeenCalledTimes(1); + expect(mockAdapterExecute).toHaveBeenCalledTimes(2); }); it("suppresses normal wakeups while allowing comment interaction wakes under a pause hold", async () => {