fix(engine): recover restart-orphaned review steps in one cycle, raise fix budget
FN-8603 sat in-review for ~36 minutes after an engine restart killed its Code Review session 34 seconds in. It did recover on its own; the cost was latency, not a terminal park. Sweep ordering. reconcile-orphaned-pending-step-results PRODUCES the failed results that recover-failed-pre-merge-steps CONSUMES, but in the periodic maintenance list it ran ~15 entries after it. A step orphaned in cycle N was therefore rewritten to failed only after recovery had already scanned, so nothing re-ran it until cycle N+1. Moved it immediately before its consumer and removed the now-duplicated later entry. Startup recovery already ordered the two correctly. Post-review fix budget. Default raised 3 -> 10 per operator request. Three passes is below the observed convergence length for the gates this fallback actually governs -- Browser Verification and custom optional gates -- since Plan Review and Code Review already resolve to "unbounded" when unset, and exhausting the budget parks the card for a human. The declaration default and five inline `settings.maxPostReviewFixes ?? 3` call sites in executor.ts/self-healing.ts had drifted into separate literals, so raising one alone would have left every unset-settings path on the old value; they now share the exported DEFAULT_MAX_POST_REVIEW_FIXES. Not done, and why. Re-dispatching a restart-orphaned lease immediately at startup is the change that would close the remaining ~14-minute wait, but it is unsound as specified: liveness is judged by a 15-minute lease-staleness floor because leases carry no node attribution, so treating a pre-boot lease as dead would let one node orphan another node's genuinely running review. Needs a node id on the lease record first. Left the floor intact. Verified: tsc clean on core and engine, pnpm lint clean, pnpm test:gate green, self-healing orphaned-pending-step-results and optional-step-revision suites green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
7
.changeset/review-restart-recovery-latency.md
Normal file
7
.changeset/review-restart-recovery-latency.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Reviews stalled by an engine restart now recover in one self-healing cycle instead of ~36 minutes.
|
||||||
|
category: fix
|
||||||
|
dev: Moves `reconcile-orphaned-pending-step-results` ahead of `recover-failed-pre-merge-steps` in the periodic maintenance list (it produces the `failed` results that step consumes; it previously ran ~15 entries later, so an orphan found in cycle N was not re-dispatched until cycle N+1) and removes the now-duplicated later entry. Raises the `maxPostReviewFixes` default 3 -> 10 and routes the five inline `?? 3` fallbacks in executor.ts/self-healing.ts through the new exported `DEFAULT_MAX_POST_REVIEW_FIXES` so the declaration default and the unset-settings paths cannot drift again. Plan Review and Code Review are unaffected — they already resolve to "unbounded" when unset.
|
||||||
@@ -140,8 +140,15 @@ export const BUILTIN_MOVED_WORKFLOW_SETTINGS: WorkflowSettingDefinition[] = [
|
|||||||
/*
|
/*
|
||||||
* FNXC:WorkflowOptionalStepCycle 2026-06-29-17:55:
|
* FNXC:WorkflowOptionalStepCycle 2026-06-29-17:55:
|
||||||
* This global budget remains the fallback for custom optional gates and explicitly capped built-in gates. Built-in Code Review now sets `maxRevisions: "unbounded"` so ordinary reviewer feedback keeps recovering instead of terminal-failing after three passes.
|
* This global budget remains the fallback for custom optional gates and explicitly capped built-in gates. Built-in Code Review now sets `maxRevisions: "unbounded"` so ordinary reviewer feedback keeps recovering instead of terminal-failing after three passes.
|
||||||
|
*
|
||||||
|
* FNXC:WorkflowOptionalStepCycle 2026-07-26-19:35:
|
||||||
|
* Raised 3 -> 10 (operator request). Three passes is below the observed convergence length for
|
||||||
|
* Browser Verification and custom gates — the gates this fallback actually governs, since
|
||||||
|
* Plan Review and Code Review resolve to "unbounded" when unset. Exhausting the budget parks
|
||||||
|
* the card for a human, so a too-low cap converts "needs another pass" into operator toil.
|
||||||
|
* This is a fallback, not a ceiling: an explicit workflow value or node `maxRevisions` still wins.
|
||||||
*/
|
*/
|
||||||
default: 3,
|
default: 10,
|
||||||
description: "Maximum automatic fix passes after review/optional-step feedback; the step re-runs each pass until it passes or this budget is exhausted.",
|
description: "Maximum automatic fix passes after review/optional-step feedback; the step re-runs each pass until it passes or this budget is exhausted.",
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -539,6 +546,15 @@ export const BUILTIN_REVIEW_REVISION_SETTINGS: WorkflowSettingDefinition[] = [
|
|||||||
* convention `metaTaskStallAutoCloseMs` already uses for a comparable stall
|
* convention `metaTaskStallAutoCloseMs` already uses for a comparable stall
|
||||||
* judgment call elsewhere in this codebase.
|
* judgment call elsewhere in this codebase.
|
||||||
*/
|
*/
|
||||||
|
/*
|
||||||
|
FNXC:WorkflowOptionalStepCycle 2026-07-26-19:38:
|
||||||
|
Single source for the post-review fix fallback. The `maxPostReviewFixes` declaration default above and
|
||||||
|
the engine's inline `settings.maxPostReviewFixes ?? N` call sites had drifted apart as two separate
|
||||||
|
literal 3s, so raising the declaration alone would have left every unset-settings path on the old
|
||||||
|
value. Import this rather than re-inlining a number.
|
||||||
|
*/
|
||||||
|
export const DEFAULT_MAX_POST_REVIEW_FIXES = 10;
|
||||||
|
|
||||||
export const DEFAULT_PLANNER_OVERSEER_EXECUTOR_STUCK_AFTER_MS = 2 * 60 * 60 * 1000;
|
export const DEFAULT_PLANNER_OVERSEER_EXECUTOR_STUCK_AFTER_MS = 2 * 60 * 60 * 1000;
|
||||||
|
|
||||||
export const PLANNER_HEARTBEAT_PATROL_ENABLED_SETTING_ID = "plannerHeartbeatPatrolEnabled";
|
export const PLANNER_HEARTBEAT_PATROL_ENABLED_SETTING_ID = "plannerHeartbeatPatrolEnabled";
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ export {
|
|||||||
BUILTIN_MOVED_WORKFLOW_SETTINGS,
|
BUILTIN_MOVED_WORKFLOW_SETTINGS,
|
||||||
BUILTIN_TRIAGE_POLICY_SETTINGS,
|
BUILTIN_TRIAGE_POLICY_SETTINGS,
|
||||||
BUILTIN_OVERSIGHT_SETTINGS,
|
BUILTIN_OVERSIGHT_SETTINGS,
|
||||||
|
DEFAULT_MAX_POST_REVIEW_FIXES,
|
||||||
DEFAULT_PLANNER_OVERSEER_EXECUTOR_STUCK_AFTER_MS,
|
DEFAULT_PLANNER_OVERSEER_EXECUTOR_STUCK_AFTER_MS,
|
||||||
PLANNER_HEARTBEAT_PATROL_ENABLED_SETTING_ID,
|
PLANNER_HEARTBEAT_PATROL_ENABLED_SETTING_ID,
|
||||||
renderTriagePolicyPlaceholders,
|
renderTriagePolicyPlaceholders,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { existsSync, lstatSync, realpathSync } from "node:fs";
|
|||||||
import { readFile, rm, writeFile } from "node:fs/promises";
|
import { readFile, rm, writeFile } from "node:fs/promises";
|
||||||
import type { TaskStore, Task, TaskDetail, TaskTokenUsage, StepStatus, Settings, WorkflowStep, MissionStore, AsyncMissionStore, Slice, AgentState, AgentCapability, RunMutationContext, AgentHeartbeatConfig, Agent, AgentMemoryInclusionMode, ProjectSettings, MergeResult, WorkflowIrNode, WorkflowIrNodeKind, WorkflowStepResult as CoreWorkflowStepResult, ThinkingLevel } from "@fusion/core";
|
import type { TaskStore, Task, TaskDetail, TaskTokenUsage, StepStatus, Settings, WorkflowStep, MissionStore, AsyncMissionStore, Slice, AgentState, AgentCapability, RunMutationContext, AgentHeartbeatConfig, Agent, AgentMemoryInclusionMode, ProjectSettings, MergeResult, WorkflowIrNode, WorkflowIrNodeKind, WorkflowStepResult as CoreWorkflowStepResult, ThinkingLevel } from "@fusion/core";
|
||||||
import { getUnmetSchedulingDependencies } from "./scheduler.js";
|
import { getUnmetSchedulingDependencies } from "./scheduler.js";
|
||||||
import { RetryStormError, serializeRetryStormError, evaluateCompletedPromotionFailureProvenance, evaluateSkipBypassTaint, resolveWorkflowIrForTask, evaluateForeachMergeProof, resolveCompleteColumn, resolveMergeOrchestrationColumn, resolveReboundTarget, resolveColumnAgentBinding, resolveEffectiveAgent, instanceNodeId, getWorkflowExtensionRegistry, getBuiltinWorkflow, parseNoOpCompletionMarker, allowsAutoMergeProcessing, resolveEffectiveAutoMerge, isLiveSharedBranchGroupMemberIntegration, resolveMaxAutoMergeRetries, resolveMaxConsecutiveToolFailureRetries, resolveConsecutiveToolFailureRetryBackoffMs, resolveConsecutiveToolFailureThreshold, resolveExecutorEscalationTarget, resolveOptionalStepRevisionBudget, resolveOptionalReviewRevisionBudget, COMPLETION_SUMMARY_NODE_ID, upsertWorkflowStepResult, AWAITING_APPROVAL_PAUSE_REASON, THINKING_LEVELS, ACTIVE_WORKFLOW_WORK_ITEM_STATES, AgentStore, resolveExecutorFallbackModel } from "@fusion/core";
|
import { RetryStormError, serializeRetryStormError, evaluateCompletedPromotionFailureProvenance, evaluateSkipBypassTaint, resolveWorkflowIrForTask, evaluateForeachMergeProof, resolveCompleteColumn, resolveMergeOrchestrationColumn, resolveReboundTarget, resolveColumnAgentBinding, resolveEffectiveAgent, instanceNodeId, getWorkflowExtensionRegistry, getBuiltinWorkflow, parseNoOpCompletionMarker, allowsAutoMergeProcessing, resolveEffectiveAutoMerge, isLiveSharedBranchGroupMemberIntegration, resolveMaxAutoMergeRetries, resolveMaxConsecutiveToolFailureRetries, resolveConsecutiveToolFailureRetryBackoffMs, resolveConsecutiveToolFailureThreshold, resolveExecutorEscalationTarget, resolveOptionalStepRevisionBudget, resolveOptionalReviewRevisionBudget, DEFAULT_MAX_POST_REVIEW_FIXES, COMPLETION_SUMMARY_NODE_ID, upsertWorkflowStepResult, AWAITING_APPROVAL_PAUSE_REASON, THINKING_LEVELS, ACTIVE_WORKFLOW_WORK_ITEM_STATES, AgentStore, resolveExecutorFallbackModel } from "@fusion/core";
|
||||||
import { finalizeProvenAutoMergeTask } from "./auto-merge-finalization.js";
|
import { finalizeProvenAutoMergeTask } from "./auto-merge-finalization.js";
|
||||||
import { mergeEffectiveSettings } from "./effective-settings.js";
|
import { mergeEffectiveSettings } from "./effective-settings.js";
|
||||||
import { generateFeatureVideo, type GenerateFeatureVideoOptions } from "./review-artifacts/feature-video.js";
|
import { generateFeatureVideo, type GenerateFeatureVideoOptions } from "./review-artifacts/feature-video.js";
|
||||||
@@ -5038,9 +5038,9 @@ export class TaskExecutor {
|
|||||||
optionalGroupId: info.nodeId ?? "plan-review",
|
optionalGroupId: info.nodeId ?? "plan-review",
|
||||||
workflowSettings: settings as Record<string, unknown>,
|
workflowSettings: settings as Record<string, unknown>,
|
||||||
nodeMaxRevisions: info.maxRevisions,
|
nodeMaxRevisions: info.maxRevisions,
|
||||||
fallbackMaxRevisions: settings.maxPostReviewFixes ?? 3,
|
fallbackMaxRevisions: settings.maxPostReviewFixes ?? DEFAULT_MAX_POST_REVIEW_FIXES,
|
||||||
});
|
});
|
||||||
const budget = resolveOptionalStepRevisionBudget(maxRevisions, settings.maxPostReviewFixes ?? 3);
|
const budget = resolveOptionalStepRevisionBudget(maxRevisions, settings.maxPostReviewFixes ?? DEFAULT_MAX_POST_REVIEW_FIXES);
|
||||||
if (!budget.unbounded && (!Number.isFinite(budget.max) || budget.max <= 0)) {
|
if (!budget.unbounded && (!Number.isFinite(budget.max) || budget.max <= 0)) {
|
||||||
// FNXC:RemediationVisibility 2026-07-26-19:20 (FN-8596 follow-up): returning false here
|
// FNXC:RemediationVisibility 2026-07-26-19:20 (FN-8596 follow-up): returning false here
|
||||||
// makes the graph's plan-replan node fail with `remediation-not-scheduled` and leaves the
|
// makes the graph's plan-replan node fail with `remediation-not-scheduled` and leaves the
|
||||||
@@ -5130,9 +5130,9 @@ export class TaskExecutor {
|
|||||||
optionalGroupId: info.nodeId ?? "",
|
optionalGroupId: info.nodeId ?? "",
|
||||||
workflowSettings: settings as Record<string, unknown>,
|
workflowSettings: settings as Record<string, unknown>,
|
||||||
nodeMaxRevisions: info.maxRevisions,
|
nodeMaxRevisions: info.maxRevisions,
|
||||||
fallbackMaxRevisions: settings.maxPostReviewFixes ?? 3,
|
fallbackMaxRevisions: settings.maxPostReviewFixes ?? DEFAULT_MAX_POST_REVIEW_FIXES,
|
||||||
});
|
});
|
||||||
const budget = resolveOptionalStepRevisionBudget(maxRevisions, settings.maxPostReviewFixes ?? 3);
|
const budget = resolveOptionalStepRevisionBudget(maxRevisions, settings.maxPostReviewFixes ?? DEFAULT_MAX_POST_REVIEW_FIXES);
|
||||||
if (!budget.unbounded && (!Number.isFinite(budget.max) || budget.max <= 0)) {
|
if (!budget.unbounded && (!Number.isFinite(budget.max) || budget.max <= 0)) {
|
||||||
executorLog.warn(
|
executorLog.warn(
|
||||||
`${taskId}: pre-merge remediation NOT scheduled for step "${info.stepName}" — revision budget is zero/invalid (max=${String(budget.max)}). Card left parked.`,
|
`${taskId}: pre-merge remediation NOT scheduled for step "${info.stepName}" — revision budget is zero/invalid (max=${String(budget.max)}). Card left parked.`,
|
||||||
@@ -9694,7 +9694,7 @@ export class TaskExecutor {
|
|||||||
target: CoreWorkflowStepResult,
|
target: CoreWorkflowStepResult,
|
||||||
): Promise<{ unbounded: boolean; max: number; label: string; key: string; stepName?: string; attempts: number }> {
|
): Promise<{ unbounded: boolean; max: number; label: string; key: string; stepName?: string; attempts: number }> {
|
||||||
const settings = await mergeEffectiveSettings(this.store, task, await this.store.getSettings());
|
const settings = await mergeEffectiveSettings(this.store, task, await this.store.getSettings());
|
||||||
const fallback = settings.maxPostReviewFixes ?? 3;
|
const fallback = settings.maxPostReviewFixes ?? DEFAULT_MAX_POST_REVIEW_FIXES;
|
||||||
let rawMaxRevisions: unknown;
|
let rawMaxRevisions: unknown;
|
||||||
try {
|
try {
|
||||||
const ir = await resolveWorkflowIrForTask(this.store, task.id);
|
const ir = await resolveWorkflowIrForTask(this.store, task.id);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync,
|
|||||||
import { readFile } from "node:fs/promises";
|
import { readFile } from "node:fs/promises";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { isAbsolute, join, relative, resolve } from "node:path";
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
||||||
import { resolveColumnFlags, IN_REVIEW_STALL_DEADLOCK_LOG_PREFIX, IN_REVIEW_STALL_LOG_PREFIX, IN_REVIEW_STALL_TERMINAL_LOG_PREFIX, allowsAutoMergeProcessing, resolveEffectiveAutoMerge, countRecentIdenticalStallEntries, detectDependencyCycle, detectSelfDefeatingDependency, evaluateNoCommitsNoOpFinalize, evaluateCompletedPromotionFailureProvenance, evaluateSkipBypassTaint, getInReviewStalledSignal, getInReviewStallReason, getPrimaryPrInfo, getStalePausedReviewSignal, getStalePausedTodoSignal, getTaskHardMergeBlocker, getTaskMergeBlocker, isEphemeralAgent, isMergeRequestContractShadowEnabled, isWorkflowColumnsEnabled, isWorkspaceTask, isSharedBranchGroupMemberIntegration, isNearDuplicateCanonicalInactive, parseExplicitDuplicateMarker, flagTriageDuplicate, isTriageDuplicateKeepAcknowledged, resolveMaxAutoMergeRetries, resolveOptionalStepRevisionBudget, resolveOptionalReviewRevisionBudget, resolveWorkflowIrForTask, resolveReboundTarget, planLegacyAdoption, resolveOrphanedPendingStepResults, classifyReviewLease, PLAN_REVIEW_LEASE_STALENESS_MS, ACTIVE_WORKFLOW_WORK_ITEM_STATES, AWAITING_APPROVAL_PAUSE_REASON, type Agent, type AgentStore, type ChatStore, type MessageStore, type TaskStore, type Settings, type Task, type MergeDetails, type TaskPriority, type MergeResult, type WorkflowStepResult } from "@fusion/core";
|
import { resolveColumnFlags, IN_REVIEW_STALL_DEADLOCK_LOG_PREFIX, IN_REVIEW_STALL_LOG_PREFIX, IN_REVIEW_STALL_TERMINAL_LOG_PREFIX, allowsAutoMergeProcessing, resolveEffectiveAutoMerge, countRecentIdenticalStallEntries, detectDependencyCycle, detectSelfDefeatingDependency, evaluateNoCommitsNoOpFinalize, evaluateCompletedPromotionFailureProvenance, evaluateSkipBypassTaint, getInReviewStalledSignal, getInReviewStallReason, getPrimaryPrInfo, getStalePausedReviewSignal, getStalePausedTodoSignal, getTaskHardMergeBlocker, getTaskMergeBlocker, isEphemeralAgent, isMergeRequestContractShadowEnabled, isWorkflowColumnsEnabled, isWorkspaceTask, isSharedBranchGroupMemberIntegration, isNearDuplicateCanonicalInactive, parseExplicitDuplicateMarker, flagTriageDuplicate, isTriageDuplicateKeepAcknowledged, resolveMaxAutoMergeRetries, resolveOptionalStepRevisionBudget, resolveOptionalReviewRevisionBudget, resolveWorkflowIrForTask, resolveReboundTarget, planLegacyAdoption, resolveOrphanedPendingStepResults, classifyReviewLease, PLAN_REVIEW_LEASE_STALENESS_MS, DEFAULT_MAX_POST_REVIEW_FIXES, ACTIVE_WORKFLOW_WORK_ITEM_STATES, AWAITING_APPROVAL_PAUSE_REASON, type Agent, type AgentStore, type ChatStore, type MessageStore, type TaskStore, type Settings, type Task, type MergeDetails, type TaskPriority, type MergeResult, type WorkflowStepResult } from "@fusion/core";
|
||||||
import { finalizePlanningSegment } from "@fusion/core";
|
import { finalizePlanningSegment } from "@fusion/core";
|
||||||
import type { MeshLeaseManager } from "./mesh-lease-manager.js";
|
import type { MeshLeaseManager } from "./mesh-lease-manager.js";
|
||||||
import { createLogger, schedulerLog } from "./logger.js";
|
import { createLogger, schedulerLog } from "./logger.js";
|
||||||
@@ -2882,6 +2882,18 @@ export class SelfHealingManager {
|
|||||||
{ name: "recover-stranded-completed-todo", fn: () => this.recoverStrandedCompletedTodoTasks() },
|
{ name: "recover-stranded-completed-todo", fn: () => this.recoverStrandedCompletedTodoTasks() },
|
||||||
{ name: "recover-advanced-triage", fn: () => this.recoverAdvancedTriageTasks() },
|
{ name: "recover-advanced-triage", fn: () => this.recoverAdvancedTriageTasks() },
|
||||||
{ name: "recover-stale-incomplete-review", fn: () => this.recoverStaleIncompleteReviewTasks() },
|
{ name: "recover-stale-incomplete-review", fn: () => this.recoverStaleIncompleteReviewTasks() },
|
||||||
|
/*
|
||||||
|
FNXC:OrphanedPendingSteps 2026-07-26-19:20:
|
||||||
|
Ordering is load-bearing: this sweep PRODUCES the `failed` results that
|
||||||
|
`recover-failed-pre-merge-steps` CONSUMES, so it must run first. It used to sit ~15
|
||||||
|
entries later in this list, which meant a step orphaned in cycle N was rewritten to
|
||||||
|
`failed` only after recovery had already scanned — nothing re-ran it until cycle N+1.
|
||||||
|
Combined with the lease-staleness wait that is a multi-cycle hole for a card whose
|
||||||
|
review session died (observed: FN-8603 sat ~36 min after an engine restart killed its
|
||||||
|
Code Review session 34s in). Startup recovery already orders these two correctly.
|
||||||
|
Keep them adjacent and in this order.
|
||||||
|
*/
|
||||||
|
{ name: "reconcile-orphaned-pending-step-results", fn: () => this.reconcileOrphanedPendingStepResults() },
|
||||||
{ name: "recover-failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps() },
|
{ name: "recover-failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps() },
|
||||||
{ name: "recover-missing-worktree-review-failures", fn: () => this.recoverMissingWorktreeReviewFailures() },
|
{ name: "recover-missing-worktree-review-failures", fn: () => this.recoverMissingWorktreeReviewFailures() },
|
||||||
{ name: "recover-interrupted-merging", fn: () => this.recoverInterruptedMergingTasks() },
|
{ name: "recover-interrupted-merging", fn: () => this.recoverInterruptedMergingTasks() },
|
||||||
@@ -2897,7 +2909,10 @@ export class SelfHealingManager {
|
|||||||
// steady-state — a step session can die without an engine restart, and startup-only
|
// steady-state — a step session can die without an engine restart, and startup-only
|
||||||
// cadence left that case riding the 3×30-min stall escalator to a deadlock park.
|
// cadence left that case riding the 3×30-min stall escalator to a deadlock park.
|
||||||
// Live sessions register their worktree path, so the liveness veto holds here.
|
// Live sessions register their worktree path, so the liveness veto holds here.
|
||||||
{ name: "reconcile-orphaned-pending-step-results", fn: () => this.reconcileOrphanedPendingStepResults() },
|
// FNXC:OrphanedPendingSteps 2026-07-26-19:22: this entry MOVED up to immediately
|
||||||
|
// before `recover-failed-pre-merge-steps` (see the ordering note there). It is not
|
||||||
|
// duplicated here — running it twice per cycle would re-scan every in-review task for
|
||||||
|
// no benefit.
|
||||||
{ name: "reconcile-stranded-hold-continuations", fn: () => this.reconcileStrandedHoldContinuations() },
|
{ name: "reconcile-stranded-hold-continuations", fn: () => this.reconcileStrandedHoldContinuations() },
|
||||||
{ name: "recover-mergeable-review", fn: () => this.recoverMergeableReviewTasks() },
|
{ name: "recover-mergeable-review", fn: () => this.recoverMergeableReviewTasks() },
|
||||||
// FNXC:Workspace 2026-06-22-09:30 (Phase D U1) — workspace-mode reconcilers.
|
// FNXC:Workspace 2026-06-22-09:30 (Phase D U1) — workspace-mode reconcilers.
|
||||||
@@ -7770,7 +7785,7 @@ export class SelfHealingManager {
|
|||||||
};
|
};
|
||||||
for (const task of tasks) {
|
for (const task of tasks) {
|
||||||
const eff = await mergeEffectiveSettings(this.store, task, settings);
|
const eff = await mergeEffectiveSettings(this.store, task, settings);
|
||||||
const fallback = eff.maxPostReviewFixes ?? 3;
|
const fallback = eff.maxPostReviewFixes ?? DEFAULT_MAX_POST_REVIEW_FIXES;
|
||||||
let rawMaxRevisions: unknown;
|
let rawMaxRevisions: unknown;
|
||||||
const target = latestFailedPreMergeStep(task);
|
const target = latestFailedPreMergeStep(task);
|
||||||
if (target?.workflowStepId) {
|
if (target?.workflowStepId) {
|
||||||
|
|||||||
Reference in New Issue
Block a user