The executor's conflict-recovery paths (cleanupConflictingWorktree,
handleBranchConflict, and tryCreateWorktree's live-foreign/stale-resolved
branches) could force-remove a worktree even when it was currently bound
to an active executor session. This caused the FN-4781/FN-4804 cascade:
- 'Execution blocked: assigned worktree path disappeared mid-task' as
git deleted the live agent's filesystem out from under it
- Two parallel runs for the same task alive simultaneously, with the
second run started in a fresh worktree while the first was still
holding the old session
- Cross-task log attribution (an FN-4804 runContext writing to FN-4781)
- Post-merge 'branch tip misbound but content found on main via trailer'
rescues firing on every successful merge as the bookkeeping was
corrupted mid-merge
Adds a hard liveness gate centralized in findActiveWorktreeOwner(), which
checks both the in-memory activeWorktrees map and the DB for non-done,
non-paused, in-progress tasks bound to the worktree. The gate fires at
two points:
1. cleanupConflictingWorktree returns false (refuses removal) when an
active owner is found, logging an FN-4811 refusal entry.
2. handleBranchConflict short-circuits to 'sticky' BEFORE invoking
inspectBranchConflict, because some inspection branches force-remove
unconditionally.
When cleanup is refused, the live-foreign and stale-resolved branches in
tryCreateWorktree now FALL THROUGH to the suffix-rename path (rather
than returning null) so the requesting task can still proceed without
disturbing the live owner.
Tests:
- New reliability-interactions backstop at
src/__tests__/reliability-interactions/active-worktree-removal-liveness.test.ts
covers findActiveWorktreeOwner (5 cases: in-memory match, requesting
task excluded, DB-level match, paused exclusion, terminal-column
exclusion, self-exclusion), cleanupConflictingWorktree gate (3 cases:
in-memory refuse, DB refuse, no-owner proceed), and handleBranchConflict
gate (2 cases: short-circuit + inspection-skipped, no-owner proceeds).
- Updates existing executor-worktree.test.ts assertion that was
documenting the bug behavior (force-removing active worktree) to match
the new contract (refuses + falls through to suffix-rename).
Full engine suite: 307 files, 5035 tests pass.
Fusion-Task-Id: FN-4811
1021 B
@runfusion/fusion
| @runfusion/fusion |
|---|
| patch |
fix(FN-4811): refuse to force-remove worktrees actively bound to live sessions
Adds a hard liveness gate to the executor's conflict-recovery paths so that
cleanupConflictingWorktree and handleBranchConflict refuse to remove a
worktree that is currently bound to an active executor session — either via
the in-memory activeWorktrees map or via a non-done, non-paused
in-progress task in the store. When the requesting task has
executorAllowSiblingBranchRename, the recovery flow now falls through to
the suffix-rename path instead of force-removing the live owner's worktree.
This is the canonical fix for the FN-4781/FN-4804 cascade:
"assigned worktree path disappeared mid-task", two parallel runs for the
same task alive simultaneously, cross-task contamination, and post-merge
"branch tip misbound but content found on main" rescues firing on every
successful merge. The new findActiveWorktreeOwner() helper centralizes
the liveness check across both gating points.