diff --git a/server/src/__tests__/issue-blocker-attention.test.ts b/server/src/__tests__/issue-blocker-attention.test.ts index 87608ad3..13480b66 100644 --- a/server/src/__tests__/issue-blocker-attention.test.ts +++ b/server/src/__tests__/issue-blocker-attention.test.ts @@ -255,6 +255,49 @@ describeEmbeddedPostgres("issue blocker attention", () => { }); }); + it("ignores cancelled direct children when counting unresolved blocker attention", async () => { + const { companyId, agentId } = await createCompany("PBD"); + const parentId = await insertIssue({ companyId, identifier: "PBD-1", title: "Parent", status: "blocked" }); + const activeBlockerOneId = await insertIssue({ + companyId, + identifier: "PBD-2", + title: "Running dependency one", + status: "todo", + assigneeAgentId: agentId, + }); + const activeBlockerTwoId = await insertIssue({ + companyId, + identifier: "PBD-3", + title: "Running dependency two", + status: "todo", + assigneeAgentId: agentId, + }); + await insertIssue({ + companyId, + identifier: "PBD-4", + title: "Cancelled child", + status: "cancelled", + parentId, + assigneeAgentId: agentId, + }); + await block({ companyId, blockerIssueId: activeBlockerOneId, blockedIssueId: parentId }); + await block({ companyId, blockerIssueId: activeBlockerTwoId, blockedIssueId: parentId }); + await activeRun({ companyId, agentId, issueId: activeBlockerOneId }); + await activeRun({ companyId, agentId, issueId: activeBlockerTwoId }); + + const parent = (await svc.list(companyId, { status: "blocked" })).find((issue) => issue.id === parentId); + + expect(parent?.blockerAttention).toMatchObject({ + state: "covered", + reason: "active_dependency", + unresolvedBlockerCount: 2, + coveredBlockerCount: 2, + stalledBlockerCount: 0, + attentionBlockerCount: 0, + }); + expect(parent?.blockerAttention?.sampleBlockerIdentifier).not.toBe("PBD-4"); + }); + it("covers recursive blocker chains when the downstream leaf has active work", async () => { const { companyId, agentId } = await createCompany("PBR"); const parentId = await insertIssue({ companyId, identifier: "PBR-1", title: "Parent", status: "blocked" }); diff --git a/server/src/services/issues.ts b/server/src/services/issues.ts index 5768d09c..7060cec9 100644 --- a/server/src/services/issues.ts +++ b/server/src/services/issues.ts @@ -1204,6 +1204,7 @@ const BLOCKER_ATTENTION_ACTIVE_WAKE_STATUSES = ["queued", "deferred_issue_execut const BLOCKER_ATTENTION_PENDING_INTERACTION_STATUSES = ["pending"]; const BLOCKER_ATTENTION_PENDING_APPROVAL_STATUSES = ["pending", "revision_requested"]; const BLOCKER_ATTENTION_OPEN_RECOVERY_ORIGIN_KIND = "harness_liveness_escalation"; +const BLOCKER_ATTENTION_CHILD_TERMINAL_STATUSES = ["done", "cancelled"]; const PRODUCTIVITY_REVIEW_ORIGIN_KIND = "issue_productivity_review"; const PRODUCTIVITY_REVIEW_TERMINAL_STATUSES = ["done", "cancelled"]; const PRODUCTIVITY_REVIEW_ACTIVITY_ACTIONS = [ @@ -1636,7 +1637,7 @@ async function listIssueBlockerAttentionMap( and( eq(issues.companyId, companyId), inArray(issues.parentId, chunk), - ne(issues.status, "done"), + notInArray(issues.status, BLOCKER_ATTENTION_CHILD_TERMINAL_STATUSES), ), ); const [explicitBlockerRows, childRows] = await Promise.all([