diff --git a/server/src/__tests__/issues-service.test.ts b/server/src/__tests__/issues-service.test.ts index 3ef21b87c59..2dc65b389f8 100644 --- a/server/src/__tests__/issues-service.test.ts +++ b/server/src/__tests__/issues-service.test.ts @@ -3215,7 +3215,7 @@ describeEmbeddedPostgres("issueService blockers and dependency wake readiness", }); }); - it("ignores unattributed pre-backfill workspace operations when checking blocker readiness", async () => { + it("keeps dependents blocked on unattributed workspace operations for the blocker workspace", async () => { const { companyId, executionWorkspaceId, @@ -3232,6 +3232,22 @@ describeEmbeddedPostgres("issueService blockers and dependency wake readiness", startedAt: new Date("2026-05-23T22:00:00.000Z"), }); + await expect(svc.listWakeableBlockedDependents(blockerId)).resolves.toEqual([]); + await expect(svc.getDependencyReadiness(dependentId)).resolves.toMatchObject({ + isDependencyReady: false, + pendingFinalizeBlockerIssueIds: [blockerId], + unresolvedBlockerIssueIds: [blockerId], + }); + + await db.insert(workspaceOperations).values({ + companyId, + executionWorkspaceId, + issueId: null, + phase: "workspace_finalize", + status: "succeeded", + startedAt: new Date("2026-05-23T22:05:00.000Z"), + }); + await expect(svc.listWakeableBlockedDependents(blockerId)).resolves.toEqual([ expect.objectContaining({ id: dependentId, @@ -3241,7 +3257,6 @@ describeEmbeddedPostgres("issueService blockers and dependency wake readiness", await expect(svc.getDependencyReadiness(dependentId)).resolves.toMatchObject({ isDependencyReady: true, pendingFinalizeBlockerIssueIds: [], - unresolvedBlockerIssueIds: [], }); }); diff --git a/server/src/services/issues.ts b/server/src/services/issues.ts index c61132e3174..81fb4bbcd96 100644 --- a/server/src/services/issues.ts +++ b/server/src/services/issues.ts @@ -686,6 +686,9 @@ async function listPendingFinalizeBlockerIssueIds( const blockerIssueIds = [...new Set(blockerWorkspacePairs.map((pair) => pair.blockerIssueId))]; const executionWorkspaceIds = [...new Set(blockerWorkspacePairs.map((pair) => pair.executionWorkspaceId))]; if (blockerIssueIds.length === 0 || executionWorkspaceIds.length === 0) return pending; + const blockerWorkspaceKeys = new Set( + blockerWorkspacePairs.map((pair) => `${pair.blockerIssueId}:${pair.executionWorkspaceId}`), + ); const rows = await dbOrTx .select({ @@ -699,28 +702,36 @@ async function listPendingFinalizeBlockerIssueIds( .where( and( eq(workspaceOperations.companyId, companyId), - inArray(workspaceOperations.issueId, blockerIssueIds), inArray(workspaceOperations.executionWorkspaceId, executionWorkspaceIds), + or(inArray(workspaceOperations.issueId, blockerIssueIds), isNull(workspaceOperations.issueId)), ), ); const latestByBlockerWorkspace = new Map(); for (const row of rows) { - if (!row.issueId || !row.executionWorkspaceId) continue; - const key = `${row.issueId}:${row.executionWorkspaceId}`; - const current = latestByBlockerWorkspace.get(key); - if (!current || row.startedAt > current.startedAt) { - latestByBlockerWorkspace.set(key, { - phase: row.phase, - status: row.status, - startedAt: row.startedAt, - }); + if (!row.executionWorkspaceId) continue; + const keys = row.issueId + ? [`${row.issueId}:${row.executionWorkspaceId}`] + : blockerWorkspacePairs + .filter((pair) => pair.executionWorkspaceId === row.executionWorkspaceId) + .map((pair) => `${pair.blockerIssueId}:${pair.executionWorkspaceId}`); + + for (const key of keys) { + if (!blockerWorkspaceKeys.has(key)) continue; + const current = latestByBlockerWorkspace.get(key); + if (!current || row.startedAt > current.startedAt) { + latestByBlockerWorkspace.set(key, { + phase: row.phase, + status: row.status, + startedAt: row.startedAt, + }); + } } } for (const pair of blockerWorkspacePairs) { const latest = latestByBlockerWorkspace.get(`${pair.blockerIssueId}:${pair.executionWorkspaceId}`); - if (!latest) continue; // no attributed ops recorded -> nothing to finalize for this blocker + if (!latest) continue; // no ops recorded -> nothing to finalize for this blocker if (latest.phase === "workspace_finalize" && latest.status === "succeeded") continue; pending.add(pair.blockerIssueId); }