From d90b7f1cb2ee212834e8afe6ab7a34fe77909bf2 Mon Sep 17 00:00:00 2001 From: Netquirk Primary Developer Date: Sat, 12 Sep 2026 05:44:30 +0000 Subject: [PATCH] fix(server): NET-6719 mark plan-approval retry runs as safe bootstrap The reaper's plan-approval continuation retry path runs after the same CAS write that terminalized the run as process_lost. The shared scheduleBoundedRetryForRun helper still applies the legacy legacyExecutionNeedsReconciliation gate, which refuses to schedule a retry when the failed run lacks explicit bootstrap evidence. The c8d2b2fd0 fix-forward added that evidence to every reaped run, but the follow-up 035f4dd79 commit removed it again so monitor-dispatch losses would still escalate to the board instead of generating spurious process_lost_retry rows. Keep both behaviours: only the interaction_continuation_infra_retry path (which has its own eligibility gates via isResolvedInteractionContinuationWakeContext and isRetryableInteractionContinuationInfrastructureFailure) needs the bootstrap evidence. Add it on the in-memory copy passed to scheduleBoundedRetryForRun inside scheduleInteractionContinuationInfrastructureRetryIfEligible so the shared gate sees safe bootstrap while the persisted row stays unchanged for the monitor-dispatch / process_lost_retry paths. NET-6719 / NET-6853 --- server/src/services/heartbeat.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index 437f16015a..f077ae9ed6 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -15830,7 +15830,21 @@ export function heartbeatService( return null; } - return scheduleBoundedRetryForRun(run, agent, { + // The reaper promoted this run to a process_lost CAS failure before any + // provider work produced output; the retry is an explicit + // infrastructure-loss replay, not an ambiguous bootstrap that the legacy + // reconciliation gate is meant to block. Mark the failed run as safe + // bootstrap evidence on the in-memory copy we hand to + // scheduleBoundedRetryForRun so the shared gate does not refuse the retry. + const runForRetry: typeof run = { + ...run, + resultJson: { + ...(parseObject(run.resultJson) ?? {}), + executionRecovery: { kind: "bootstrap", providerWorkStarted: false }, + }, + }; + + return scheduleBoundedRetryForRun(runForRetry, agent, { retryReason: INTERACTION_CONTINUATION_INFRA_RETRY_REASON, wakeReason: INTERACTION_CONTINUATION_INFRA_WAKE_REASON, maxAttempts: INTERACTION_CONTINUATION_INFRA_MAX_ATTEMPTS,