From 02b2fecdf7a08221da7493e3442768586cd7ec59 Mon Sep 17 00:00:00 2001 From: Van Echeverri Date: Wed, 3 Jun 2026 06:42:40 -0400 Subject: [PATCH] fix: resolve missing_disposition recovery loop for blocked issues with active blockers When an issue is `blocked` with unresolved first-class blockers, it has a valid disposition. But `classifySourceRecoveryRevalidation()` gated the `blocked + unresolvedBlockerCount > 0` check behind `durableSourceChange`, which was only true when status/assignee/blockers/etc. actually changed in the PATCH. Heartbeat comment-only PATCHes had `durableSourceChange = false`, so the function returned `null` (no resolution) even though the issue was correctly blocked. This caused a `missing_disposition` recovery action to stay `active` indefinitely, waking the agent on every scheduler tick. Fix: move the `blocked + unresolvedBlockerCount > 0` check before the `read_projection` early-return gate so it fires on every revalidation regardless of what changed. A blocked issue with active blockers always has a valid disposition. Co-Authored-By: Paperclip --- server/src/routes/issues.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index 85492638c7..1a39da5c9c 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -995,6 +995,17 @@ export function issueRoutes( return "Recovery action became stale because the source issue was manually moved from blocked to todo."; } + // Check blocked+blockers before any trigger-based early returns: a blocked issue with + // active first-class blockers always has a valid disposition regardless of what changed. + // This prevents recovery-action loops where the issue is correctly blocked but the + // missing_disposition action stays active because no "durable change" has occurred. + if (issue.status === "blocked") { + const readiness = await svc.getDependencyReadiness(issue.id); + if (readiness.unresolvedBlockerCount > 0) { + return "Recovery action became stale because the source issue now has unresolved first-class blockers."; + } + } + if (input.trigger === "read_projection") return null; if ( input.trigger === "comment" &&