From 5b1e1239fdcdbdcf4d8aa3a522e02bd6d95fdd30 Mon Sep 17 00:00:00 2001 From: dotta Date: Fri, 20 Mar 2026 08:58:24 -0500 Subject: [PATCH] Fix routine run assignment wakeups Co-Authored-By: Paperclip --- server/src/__tests__/routines-service.test.ts | 37 +++++++++++++++++-- server/src/routes/issues.ts | 2 +- .../src/services/issue-assignment-wakeup.ts | 2 +- server/src/services/routines.ts | 3 +- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/server/src/__tests__/routines-service.test.ts b/server/src/__tests__/routines-service.test.ts index 9240dfbf..2d07d63a 100644 --- a/server/src/__tests__/routines-service.test.ts +++ b/server/src/__tests__/routines-service.test.ts @@ -115,7 +115,20 @@ describe("routine service live-execution coalescing", () => { } }); - async function seedFixture() { + async function seedFixture(opts?: { + wakeup?: ( + agentId: string, + wakeupOpts: { + source?: string; + triggerDetail?: string; + reason?: string | null; + payload?: Record | null; + requestedByActorType?: "user" | "agent" | "system"; + requestedByActorId?: string | null; + contextSnapshot?: Record; + }, + ) => Promise; + }) { const companyId = randomUUID(); const agentId = randomUUID(); const projectId = randomUUID(); @@ -161,9 +174,9 @@ describe("routine service live-execution coalescing", () => { const svc = routineService(db, { heartbeat: { - wakeup: async (agentId, opts) => { - wakeups.push({ agentId, opts }); - return null; + wakeup: async (wakeupAgentId, wakeupOpts) => { + wakeups.push({ agentId: wakeupAgentId, opts: wakeupOpts }); + return opts?.wakeup ? opts.wakeup(wakeupAgentId, wakeupOpts) : null; }, }, }); @@ -258,6 +271,22 @@ describe("routine service live-execution coalescing", () => { ]); }); + it("waits for the assignee wakeup to be queued before returning the routine run", async () => { + let wakeupResolved = false; + const { routine, svc } = await seedFixture({ + wakeup: async () => { + await new Promise((resolve) => setTimeout(resolve, 10)); + wakeupResolved = true; + return null; + }, + }); + + const run = await svc.runRoutine(routine.id, { source: "manual" }); + + expect(run.status).toBe("issue_created"); + expect(wakeupResolved).toBe(true); + }); + it("coalesces only when the existing routine issue has a live execution run", async () => { const { agentId, companyId, issueSvc, routine, svc } = await seedFixture(); const previousRunId = randomUUID(); diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index 4173f1a6..0e19a871 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -782,7 +782,7 @@ export function issueRoutes(db: Db, storage: StorageService) { details: { title: issue.title, identifier: issue.identifier }, }); - queueIssueAssignmentWakeup({ + void queueIssueAssignmentWakeup({ heartbeat, issue, reason: "issue_assigned", diff --git a/server/src/services/issue-assignment-wakeup.ts b/server/src/services/issue-assignment-wakeup.ts index aa39d0c5..96eaa982 100644 --- a/server/src/services/issue-assignment-wakeup.ts +++ b/server/src/services/issue-assignment-wakeup.ts @@ -29,7 +29,7 @@ export function queueIssueAssignmentWakeup(input: { }) { if (!input.issue.assigneeAgentId || input.issue.status === "backlog") return; - void input.heartbeat + return input.heartbeat .wakeup(input.issue.assigneeAgentId, { source: "assignment", triggerDetail: "system", diff --git a/server/src/services/routines.ts b/server/src/services/routines.ts index 0fb8436a..f0c65c4a 100644 --- a/server/src/services/routines.ts +++ b/server/src/services/routines.ts @@ -619,7 +619,8 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup status: "issue_created", linkedIssueId: createdIssue.id, }); - queueIssueAssignmentWakeup({ + // Ensure the wake request is durably queued before reporting the routine run as created. + await queueIssueAssignmentWakeup({ heartbeat, issue: createdIssue, reason: "issue_assigned",