fix(engine): rescue auto-merge handoff from stale mergeActive entries

The 15s `scheduleMergeRetry` sweep was silently re-skipping in-review
tasks whose `mergeActive` entry leaked from a wedged prior attempt
(uncaught error inside `drainMergeQueue`, restart between push and
finally, etc.). FN-002, FN-004, FN-3898, FN-3899 all sat in in-review
until the 15-min maintenance loop logged "Auto-recovered: eligible
in-review task re-enqueued for merge".

Two changes:

* `reconcileStaleMergeActive()` runs before each 15s sweep. Any
  `mergeActive` taskId that isn't in `mergeQueue` and isn't the
  `activeMergeTaskId` is treated as leaked and dropped, so the next
  enqueue actually pushes through.
* The `task:moved → in-review` immediate handoff (`wireAutoMerge`) now
  logs every skip reason instead of returning silently, clears its own
  stale `mergeActive` entry before enqueueing, and identifies the task
  in its catch-block warning. `internalEnqueueMerge` also warns when a
  leaked entry causes a skip — the next regression won't be invisible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-09 16:33:54 -07:00
parent 7ed685a21b
commit 98b9e25ca4
3 changed files with 64 additions and 9 deletions

View File

@@ -1766,7 +1766,7 @@ describe("ProjectEngine swallowed error hardening", () => {
await vi.advanceTimersByTimeAsync(500);
expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining("Auto-merge: failed to read settings for task:moved on FN-001"),
expect.stringContaining("Auto-merge handoff (FN-001) failed: db locked"),
);
await engine.stop();