fix(FN-5092): clear transient "merging" status on self-healing auto-finalize paths

The merger maintains a single mergeActive slot per project; while a task
holds it, no other in-review task can be picked up. When the merger
sets task.status="merging" but the task is then completed via a
self-healing recovery path instead of the merger's own completeTask(),
the status field is never cleared. The mergeActive slot stays leaked
indefinitely, and the entire merger queue stalls.

Concrete failure: FN-5052 landed via FN-4499 auto-recover-branch-misbound
on 2026-05-18 15:48 with mergeConfirmed=true. Status field stayed at
"merging". 12.5 hours later, FN-5083, FN-5060, FN-5053, FN-5092, and
every other in-review task were waiting on the merger queue with no
merger activity at all.

Two self-healing → done paths were missing the status clear:

- recoverBranchMisboundInReviewTasks() (self-healing.ts ~5031):
  branch-tip-misbound recovery moves an in-review task to done after
  detecting its work landed on main via a different commit. The merger's
  completeTask() correctly clears status before moveTask(); this path
  did not.

- proven-no-op finalize in recoverNoOpReviewTasks() (self-healing.ts
  ~3460): for tasks proven already on main (FN-4500 classifier), the
  updateTask({ mergeDetails, modifiedFiles }) patch did not include
  status: null. Same leak class.

Both now follow the pattern from merger.ts completeTask() and
project-engine.ts auto-merge already-confirmed:
  updateTask({ status: null, error: null, paused: false }) before
  moveTask("done").

FN-5052 task.json patched to clear the existing stale status as part
of this commit so the running engine releases its mergeActive slot
on next sweep.

Fusion-Task-Id: FN-5092
This commit is contained in:
gsxdsm
2026-05-18 21:24:02 -07:00
parent fac81b8432
commit a25ef82e13

View File

@@ -3449,7 +3449,10 @@ export class SelfHealingManager {
mergedAt,
mergeTargetBranch: classification.baseRef,
};
await this.store.updateTask(task.id, { mergeDetails, modifiedFiles: [] });
// FN-5092 hotfix: clear transient merger-queue status alongside mergeDetails
// patch so a leaked `mergeActive` slot from a prior merge attempt cannot survive
// this auto-finalize path. Same bug class as recoverBranchMisboundInReviewTasks().
await this.store.updateTask(task.id, { mergeDetails, modifiedFiles: [], status: null, error: null, paused: false });
await this.recordIntegrityAudit(task.id, "task:integrity-reconcile-modified-files", {
reason: "proven-no-op-finalize",
clearedCount: task.modifiedFiles?.length ?? 0,
@@ -5028,6 +5031,12 @@ export class SelfHealingManager {
await this.clearCompletionBranchIfSubsumed(task, branch).catch(() => false);
// FN-5092 hotfix: clear transient merger-queue status (`status: "merging"` set
// by the original merger attempt) before transitioning to done. Without this,
// a stale `mergeActive` slot for this task leaks indefinitely and blocks the
// entire merger queue until engine restart. Mirrors the pattern in
// merger.ts completeTask() and project-engine.ts auto-merge already-confirmed path.
await this.store.updateTask(task.id, { status: null, error: null, paused: false });
const movedTask = await this.store.moveTask(task.id, "done");
this.emitTaskMerged(movedTask, { mergeConfirmed: true });
await this.store.logEntry(