diff --git a/server/src/routes/agents.ts b/server/src/routes/agents.ts index 1428fae7b8..f0e5bb351e 100644 --- a/server/src/routes/agents.ts +++ b/server/src/routes/agents.ts @@ -3996,7 +3996,7 @@ export function agentRoutes( const recoveryActionsSvc = issueRecoveryActionService(db); const rows = await issuesSvc.list(req.actor.companyId, { assigneeAgentId: req.actor.agentId, - status: "todo,in_progress,blocked", + status: "todo,in_progress,blocked,in_review", includeRoutineExecutions: true, limit: ISSUE_LIST_DEFAULT_LIMIT, }); diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index 1535317caf..c9123aa020 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -4156,6 +4156,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" &&