Prevent task worktree acquisition from handing the project root back to executors. - Add a repo-root return guard across resume, pool, and fresh acquisition paths. - Clear invalid repo-root task assignments and create a fresh worktree fallback when safe. - Share repo-root canonicalization and expand regression coverage for acquisition liveness. - Document the acquisition guard and add a patch changeset. Files changed: .changeset/fn-6922-repo-root-acquisition-guard.md | 5 + .../repo-root-task-worktree-requeue-loop.md | 9 +- .../__tests__/executor-worktree-liveness.test.ts | 35 +++ .../worktree-acquisition-secrets-env.test.ts | 9 +- .../src/__tests__/worktree-acquisition.test.ts | 91 ++++++- packages/engine/src/worktree-acquisition.ts | 272 +++++++++++++-------- packages/engine/src/worktree-pool.ts | 8 +- 7 files changed, 304 insertions(+), 125 deletions(-) Fusion-Task-Id: FN-6922 Fusion-Task-Lineage: c16cee1b-de16-4d5f-90e0-132f94fa8377
3.9 KiB
title, date, category, module, problem_type, component, symptoms, root_cause, resolution_type, severity, related_components, tags
| title | date | category | module | problem_type | component | symptoms | root_cause | resolution_type | severity | related_components | tags | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Repo-root task worktree causes executor requeue loop | 2026-06-21 | docs/solutions/logic-errors | engine worktree acquisition + executor liveness | logic_error | engine |
|
invariant_gap | code_fix | high |
|
|
Repo-root task worktree causes executor requeue loop
Problem
A recovered task can carry task.worktree that canonicalizes to the project repository root. The root is a valid Git worktree and appears in git worktree list, but it is the main checkout, not an isolated task checkout. Before FN-6861, classifyTaskWorktree(rootDir, rootDir) returned usable, so resume acquisition returned the root unchanged. The executor then rejected the same path via realpath_matches_repo_root and requeued the task, setting up an acquisition → gate → requeue loop.
Solution
Make the invariant explicit at the shared classification boundary: the project root is never a usable task worktree. classifyTaskWorktree now compares canonicalized paths and returns classification: "repo-root" for root-equal paths even when Git reports the path as registered.
Because acquireTaskWorktree already treats non-usable resume classifications as self-healable stale metadata, a root-valued task.worktree is cleared and replaced with a fresh checkout under the configured worktrees directory. FN-6922 adds the same invariant as an acquisition return postcondition: every existing, pooled, and fresh-created return path is checked immediately before returning to executor/heartbeat callers. If a return candidate canonicalizes to the project root, acquisition emits worktree:incomplete-detected with source: "acquire-return-guard", clears worktree metadata, and attempts one fresh checkout; if the fresh checkout is also root-equal, it throws RepoRootWorktreeError instead of returning the root.
The executor liveness gate remains defense-in-depth and emits structured worktree:incomplete-detected evidence if a repo-root path still reaches it.
Verification
Cover the invariant at three seams:
- Classification: real Git repo root registered in
git worktree listmust classify asrepo-root, including canonical-equal variants such as trailing slashes or symlink-normalized paths. - Acquisition: resume with
task.worktree === rootDirmust return a fresh.worktrees/*(or configured worktrees-dir) checkout and must not return the root. - Acquisition return guard: even if a classifier mock/regression marks a root path usable, or if a custom fresh backend returns the root,
acquireTaskWorktreemust either self-heal to a non-root checkout or throwRepoRootWorktreeError. - Executor diagnostics: if the root reaches the pre-session liveness gate, the audit payload must identify
classification: "repo-root", the observed path, the registered snapshot, and that the expected task-worktree pattern excludes the root.
Prevention
Registered Git worktree membership is necessary but not sufficient for task execution. Any new worktree-liveness or self-healing path should call the shared classifier and preserve the distinction between the main checkout (repo-root) and isolated task checkouts under the configured worktrees directory. Any new acquireTaskWorktree return branch must also flow through the return guard so branch-local checks cannot be the only line of defense.