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:
@@ -4987,6 +4987,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
|
|||||||
existingWake,
|
existingWake,
|
||||||
budgetBlock,
|
budgetBlock,
|
||||||
pauseHold,
|
pauseHold,
|
||||||
|
activeRoutineContinuation,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
issue
|
issue
|
||||||
? db
|
? db
|
||||||
@@ -5112,6 +5113,20 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
|
|||||||
issue
|
issue
|
||||||
? treeControlSvc.getActivePauseHoldGate(issue.companyId, issue.id)
|
? treeControlSvc.getActivePauseHoldGate(issue.companyId, issue.id)
|
||||||
: Promise.resolve(null),
|
: 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({
|
const decision = decideSuccessfulRunHandoff({
|
||||||
@@ -5127,6 +5142,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
|
|||||||
hasExplicitBlockerPath: Boolean(explicitBlocker),
|
hasExplicitBlockerPath: Boolean(explicitBlocker),
|
||||||
hasOpenRecoveryIssue: Boolean(openRecoveryIssue),
|
hasOpenRecoveryIssue: Boolean(openRecoveryIssue),
|
||||||
hasPauseHold: Boolean(pauseHold),
|
hasPauseHold: Boolean(pauseHold),
|
||||||
|
hasActiveRoutineContinuation: Boolean(activeRoutineContinuation),
|
||||||
budgetBlocked: Boolean(budgetBlock),
|
budgetBlocked: Boolean(budgetBlock),
|
||||||
idempotentWakeExists: Boolean(existingWake),
|
idempotentWakeExists: Boolean(existingWake),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ function decide(overrides: Partial<Parameters<typeof decideSuccessfulRunHandoff>
|
|||||||
hasExplicitBlockerPath: false,
|
hasExplicitBlockerPath: false,
|
||||||
hasOpenRecoveryIssue: false,
|
hasOpenRecoveryIssue: false,
|
||||||
hasPauseHold: false,
|
hasPauseHold: false,
|
||||||
|
hasActiveRoutineContinuation: false,
|
||||||
budgetBlocked: false,
|
budgetBlocked: false,
|
||||||
idempotentWakeExists: false,
|
idempotentWakeExists: false,
|
||||||
...overrides,
|
...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", () => {
|
it("does not queue when a successful run has no progress signal", () => {
|
||||||
expect(decide({ livenessState: null, detectedProgressSummary: null })).toEqual({
|
expect(decide({ livenessState: null, detectedProgressSummary: null })).toEqual({
|
||||||
kind: "skip",
|
kind: "skip",
|
||||||
|
|||||||
@@ -350,6 +350,7 @@ export function decideSuccessfulRunHandoff(input: {
|
|||||||
hasExplicitBlockerPath: boolean;
|
hasExplicitBlockerPath: boolean;
|
||||||
hasOpenRecoveryIssue: boolean;
|
hasOpenRecoveryIssue: boolean;
|
||||||
hasPauseHold: boolean;
|
hasPauseHold: boolean;
|
||||||
|
hasActiveRoutineContinuation: boolean;
|
||||||
budgetBlocked: boolean;
|
budgetBlocked: boolean;
|
||||||
idempotentWakeExists: boolean;
|
idempotentWakeExists: boolean;
|
||||||
}): SuccessfulRunHandoffDecision {
|
}): SuccessfulRunHandoffDecision {
|
||||||
@@ -376,6 +377,9 @@ export function decideSuccessfulRunHandoff(input: {
|
|||||||
if (agent.status === "paused" || agent.status === "terminated" || agent.status === "pending_approval") {
|
if (agent.status === "paused" || agent.status === "terminated" || agent.status === "pending_approval") {
|
||||||
return { kind: "skip", reason: `agent status ${agent.status} is not invokable` };
|
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)) {
|
if (!isProductiveSuccessfulRun(input)) {
|
||||||
return { kind: "skip", reason: "successful run did not produce handoff-relevant progress" };
|
return { kind: "skip", reason: "successful run did not produce handoff-relevant progress" };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user