Close the last fire-and-forget gap from the previous fixes: the task:moved (away from in-progress) and task:deleted listeners no longer call the synchronous fire-and-forget `abortInFlightTaskWork`. Instead they track an awaited disposal promise per task in `pendingTaskDisposals`. The task:moved (to in-progress) dispatch path awaits any in-flight disposal for the same task before calling `execute()`, so a fast bounce (in-progress → todo → in-progress) no longer races the conflict-cleanup path against a still-live shell. `awaitAbortInFlightTaskWork` now claims each session surface (activeSessions, activeStepExecutors, activeWorkflowStepSessions, activeSubagentSessions) synchronously before awaiting any async abort. This lets concurrent disposal calls for the same task dedupe naturally — the second call finds the maps empty and no-ops, preserving the existing single-abort/single-dispose contract that the soft-delete and user-cancel tests assert. Adds a regression test in executor-user-cancel covering the re-dispatch ordering: an immediate task:moved-to-in-progress that follows a still-running task:moved-away must wait for abort to complete before execute() runs. The legacy `abortInFlightTaskWork` is removed (no callers). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2.2 KiB
@runfusion/fusion
| @runfusion/fusion |
|---|
| patch |
fix(FN-5256): harden three independent code paths that were losing live task worktrees mid-execution and producing wrong_toplevel errors.
-
Executor stale-self-owned classifier (
reconcileSelfOwnedActiveSessionForRemoval) now requires two additional signals before dropping a same-task registry entry: a process-active probe (executingTaskLock.has) and a minimum-idle window (default 5s) since the entry was registered. This closes the pause/resume race where the new executor cycle hadn't repopulatedactiveWorktreesyet and the old session's registry entry was reaped under a still-live shell. The post-throw reconcile inremoveOwnWorktreeWithReconcileand theremoveWorktreedefensive reconcile inworktree-backend.tsroute through the same hardened path. -
Pause-before-park now synchronously awaits agent/step/workflow session disposal via a new
awaitAbortInFlightTaskWorkmethod, so by the timeparkTaskAfterWorkflowStepPausecallsmoveTask("todo")the spawned shells are already reaped and any fast re-dispatch sees a clean slate. The user-initiated pause handler ontask:updatedwas collapsed onto the same await path for the same reason. -
Self-healing
reconcileTaskWorktreeMetadatanow normalizes both sides viarealpathSync(with ENOENT fallback) before comparing the task worktree against the registered set, fixing the macOS/private/var/...false-stale flag. It additionally refuses to clearworktree/branchmetadata for in-progress or in-review tasks — those go throughtask:auto-recover-worktree-metadata-skipped-activeaudit events and leave executor-level recovery in charge. -
The
task:moved-away andtask:deletedlisteners now track an awaited disposal promise per task. A re-dispatch (task:moved→ in-progress) awaits any in-flight disposal for the same task before callingexecute(), so a fast bounce (in-progress → todo → in-progress) can no longer race the conflict-cleanup path against a still-live shell.awaitAbortInFlightTaskWorkclaims each session surface synchronously before awaiting its abort, so concurrent disposal calls dedupe naturally and the legacy fire-and-forgetabortInFlightTaskWorkhas been removed.