fix(recovery): exempt routine-parent issues from missing-disposition handoff (#8157)

Recognize active routine-parent issues as having a valid continuation path during successful-run handoff recovery. This prevents unnecessary missing-disposition corrective wakes when an active routine owns the next scheduled action.

Also keeps the routine-continuation guard before the productivity check so logs surface the decisive skip reason for both productive and non-productive routine-parent runs.

Verification:
- CI status checks passed on PR #8157
- Greptile Review passed at 5/5
- Focused recovery unit test passed locally

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
BuyWhere
2026-06-19 07:59:06 +07:00
committed by GitHub
parent 5c11725784
commit 67c98323b0
3 changed files with 44 additions and 0 deletions
+16
View File
@@ -4987,6 +4987,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
existingWake,
budgetBlock,
pauseHold,
activeRoutineContinuation,
] = await Promise.all([
issue
? db
@@ -5112,6 +5113,20 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
issue
? treeControlSvc.getActivePauseHoldGate(issue.companyId, issue.id)
: Promise.resolve(null),
issue
? db
.select({ id: routines.id })
.from(routines)
.where(
and(
eq(routines.companyId, issue.companyId),
eq(routines.parentIssueId, issue.id),
eq(routines.status, "active"),
),
)
.limit(1)
.then((rows) => rows[0] ?? null)
: Promise.resolve(null),
]);
const decision = decideSuccessfulRunHandoff({
@@ -5127,6 +5142,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
hasExplicitBlockerPath: Boolean(explicitBlocker),
hasOpenRecoveryIssue: Boolean(openRecoveryIssue),
hasPauseHold: Boolean(pauseHold),
hasActiveRoutineContinuation: Boolean(activeRoutineContinuation),
budgetBlocked: Boolean(budgetBlock),
idempotentWakeExists: Boolean(existingWake),
});
@@ -52,6 +52,7 @@ function decide(overrides: Partial<Parameters<typeof decideSuccessfulRunHandoff>
hasExplicitBlockerPath: false,
hasOpenRecoveryIssue: false,
hasPauseHold: false,
hasActiveRoutineContinuation: false,
budgetBlocked: false,
idempotentWakeExists: false,
...overrides,
@@ -130,6 +131,29 @@ describe("successful run handoff decision", () => {
});
});
it("does not queue when the issue is the recurring parent of an active routine", () => {
expect(decide({ hasActiveRoutineContinuation: true })).toEqual({
kind: "skip",
reason: "active routine continuation owns the next action",
});
expect(decide({
hasActiveRoutineContinuation: true,
detectedProgressSummary: null,
livenessState: null,
})).toEqual({
kind: "skip",
reason: "active routine continuation owns the next action",
});
expect(decide({
hasActiveRoutineContinuation: true,
livenessState: "advanced",
detectedProgressSummary: "Run produced concrete action evidence: 1 issue comment(s)",
})).toEqual({
kind: "skip",
reason: "active routine continuation owns the next action",
});
});
it("does not queue when a successful run has no progress signal", () => {
expect(decide({ livenessState: null, detectedProgressSummary: null })).toEqual({
kind: "skip",
@@ -350,6 +350,7 @@ export function decideSuccessfulRunHandoff(input: {
hasExplicitBlockerPath: boolean;
hasOpenRecoveryIssue: boolean;
hasPauseHold: boolean;
hasActiveRoutineContinuation: boolean;
budgetBlocked: boolean;
idempotentWakeExists: boolean;
}): SuccessfulRunHandoffDecision {
@@ -376,6 +377,9 @@ export function decideSuccessfulRunHandoff(input: {
if (agent.status === "paused" || agent.status === "terminated" || agent.status === "pending_approval") {
return { kind: "skip", reason: `agent status ${agent.status} is not invokable` };
}
if (input.hasActiveRoutineContinuation) {
return { kind: "skip", reason: "active routine continuation owns the next action" };
}
if (!isProductiveSuccessfulRun(input)) {
return { kind: "skip", reason: "successful run did not produce handoff-relevant progress" };
}