fix(watchdogs): follow self-review reassignment

Retarget self-mode watchdogs to the watched issue's current invokable assignee
before classification and wake dispatch.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-07-29 19:07:29 +00:00
1 parent 356dbe1e1c
commit fd2c751122
2 files changed
+35 -1

No files matched your search

@@ -407,6 +407,27 @@ describeEmbeddedPostgres("task watchdog scheduler", () => {
expect(watchdog?.triggerCount).toBe(1);
});
it("retargets a self-mode watchdog to the current assignee before waking", async () => {
const companyId = await seedCompany();
const formerAssigneeId = await seedAgent(companyId, { name: "Former Assignee" });
const currentAssigneeId = await seedAgent(companyId, { name: "Current Assignee" });
const sourceId = await seedIssue(companyId, {
identifier: "WDOG-SELF-REASSIGNED",
status: "done",
assigneeAgentId: currentAssigneeId,
});
await seedWatchdog(companyId, sourceId, formerAssigneeId, { mode: "self" });
const { service, wakes } = createService();
const result = await service.reconcileTaskWatchdogs({ companyId });
expect(result).toMatchObject({ checked: 1, triggered: 1 });
expect(wakes).toHaveLength(1);
expect(wakes[0]?.agentId).toBe(currentAssigneeId);
const [watchdog] = await db.select().from(issueWatchdogs).where(eq(issueWatchdogs.issueId, sourceId));
expect(watchdog?.watchdogAgentId).toBe(currentAssigneeId);
});
it("retries a self review when the wake was not queued", async () => {
const companyId = await seedCompany();
const agentId = await seedAgent(companyId);
+14 -1
View File
@@ -1667,7 +1667,7 @@ export function taskWatchdogService(db: Db, deps: TaskWatchdogServiceDeps = {})
}
async function evaluateWatchdog(row: IssueWatchdogRow, opts: { runId?: string | null } = {}) {
const watchdog = await markTerminalWatchdogIssueReviewed(row, opts);
let watchdog = await markTerminalWatchdogIssueReviewed(row, opts);
const sourceIssue = await db
.select()
.from(issues)
@@ -1677,6 +1677,19 @@ export function taskWatchdogService(db: Db, deps: TaskWatchdogServiceDeps = {})
return { state: "skipped" as const, reason: "watched_issue_not_applicable" };
}
if (issueWatchdogMode(watchdog) === "self" && watchdog.watchdogAgentId !== sourceIssue.assigneeAgentId) {
if (!sourceIssue.assigneeAgentId) {
return { state: "skipped" as const, reason: "self_watchdog_issue_unassigned" };
}
await assertWatchdogAgentInvokable(db, watchdog.companyId, sourceIssue.assigneeAgentId);
watchdog = await db
.update(issueWatchdogs)
.set({ watchdogAgentId: sourceIssue.assigneeAgentId, updatedAt: new Date() })
.where(eq(issueWatchdogs.id, watchdog.id))
.returning()
.then((rows) => rows[0] ?? watchdog);
}
const input = await collectClassifierInput(watchdog.companyId, watchdog);
const classification = classifyTaskWatchdogSubtree(input);
if (classification.state !== "stopped") {