fix(engine): bind self-owned worktree liveness probe (#3419)

## Summary
Supersedes #3416 — the fork head is not writable from maintainers, so
this branch carries the same fix merged onto current `main`.

## Changes
- Bind the self-owned worktree liveness probe in task-executor pure
facades (from #3416).
- Merge current main so the lifecycle-column census baseline stays
green.

## Test plan
- [x] `pnpm check:lifecycle-columns` clean after merge
- [ ] CI green

Closes context from #3416.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Fixed cleanup for self-owned worktrees so it no longer crashes before
checking whether an active session is present.
* Improved safety during worktree reconciliation by correctly verifying
that the worktree is still active before removal.

* **Documentation**
  * Added a changeset documenting the worktree cleanup fix.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: BESA-Franz <49682134+BESA-Franz@users.noreply.github.com>
This commit is contained in:
gsxdsm
2026-08-11 11:28:42 -10:00
committed by GitHub
parent a6ce7f89ef
commit 66c51038a5
2 changed files with 24 additions and 1 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Prevent worktree conflict cleanup from crashing before its active-session safety check.
category: fix
dev: Binds the executor live-worktree probe when the self-owned reconcile facade calls its extracted helper.

View File

@@ -22,7 +22,23 @@ export abstract class TaskExecutorWorktreePureFacades extends TaskExecutorState
protected async isLiveCleanupRefusal(worktreePath: string, taskId: string): Promise<boolean> { return pure.isLiveCleanupRefusal(this.activeWorktrees, this.store, worktreePath, taskId); } protected async isLiveCleanupRefusal(worktreePath: string, taskId: string): Promise<boolean> { return pure.isLiveCleanupRefusal(this.activeWorktrees, this.store, worktreePath, taskId); }
protected async cleanupStaleBranch(branch: string, taskId: string): Promise<boolean> { return pure.cleanupStaleBranch(this.rootDir, this.store, branch, taskId); } protected async cleanupStaleBranch(branch: string, taskId: string): Promise<boolean> { return pure.cleanupStaleBranch(this.rootDir, this.store, branch, taskId); }
protected async planSquashImportFromDep(...args: FacadeAfterSecond<typeof pure.planSquashImportFromDep>): ReturnType<typeof pure.planSquashImportFromDep> { return pure.planSquashImportFromDep(this.rootDir, this.store, ...args); } protected async planSquashImportFromDep(...args: FacadeAfterSecond<typeof pure.planSquashImportFromDep>): ReturnType<typeof pure.planSquashImportFromDep> { return pure.planSquashImportFromDep(this.rootDir, this.store, ...args); }
protected async reconcileSelfOwnedBeforeRemove(...args: FacadeRestArgs<typeof pure.reconcileSelfOwnedBeforeRemove>): ReturnType<typeof pure.reconcileSelfOwnedBeforeRemove> { return pure.reconcileSelfOwnedBeforeRemove(this.store, ...args); } /*
FNXC:WorktreeConflictRecovery 2026-08-10-20:31:
The free reconcile helper requires the executor's live-binding probe in addition to its store.
Bind that host-owned callback here so every two-argument facade caller receives the production
liveness guard instead of forwarding an undefined third argument into conflict cleanup.
*/
protected async reconcileSelfOwnedBeforeRemove(
worktreePath: string,
taskId: string,
): ReturnType<typeof pure.reconcileSelfOwnedBeforeRemove> {
return pure.reconcileSelfOwnedBeforeRemove(
this.store,
worktreePath,
taskId,
(ownerTaskId, path) => this.hasActiveWorktreeBinding(ownerTaskId, path),
);
}
protected async emitStaleLockAudit(...args: FacadeRestArgs<typeof pure.emitStaleLockAudit>): ReturnType<typeof pure.emitStaleLockAudit> { return pure.emitStaleLockAudit(bags.buildStaleLockRecoveryDeps(this), ...args); } protected async emitStaleLockAudit(...args: FacadeRestArgs<typeof pure.emitStaleLockAudit>): ReturnType<typeof pure.emitStaleLockAudit> { return pure.emitStaleLockAudit(bags.buildStaleLockRecoveryDeps(this), ...args); }
protected async recoverIndexLockIfStale(taskId: string, path: string, conflictInfo: { lockPath?: string; message?: string }): Promise<boolean> { return pure.recoverIndexLockIfStale(bags.buildStaleLockRecoveryDeps(this), taskId, path, conflictInfo); } protected async recoverIndexLockIfStale(taskId: string, path: string, conflictInfo: { lockPath?: string; message?: string }): Promise<boolean> { return pure.recoverIndexLockIfStale(bags.buildStaleLockRecoveryDeps(this), taskId, path, conflictInfo); }
protected async recoverStaleRegistration(taskId: string, path: string, conflictInfo: { path?: string; message?: string }): Promise<boolean> { return pure.recoverExecutorStaleRegistration(bags.buildStaleLockRecoveryDeps(this), taskId, path, conflictInfo); } protected async recoverStaleRegistration(taskId: string, path: string, conflictInfo: { path?: string; message?: string }): Promise<boolean> { return pure.recoverExecutorStaleRegistration(bags.buildStaleLockRecoveryDeps(this), taskId, path, conflictInfo); }