FN-5772: re-anchor nested task worktree paths

Fix executor worktree invariant handling by re-anchoring nested task worktree paths to the actual git top-level.

- add nested worktree root detection that only re-anchors when the top-level is a registered worktree inside the configured worktrees directory
- update executor liveness gating and verifyWorktreeInvariants to persist re-anchored task.worktree values and retry checks safely
- emit a new run-audit git mutation (worktree:reanchored) and add reliability tests/docs coverage plus a patch changeset

Files changed:
 .changeset/fn-5772-worktree-reanchor.md            |  7 +++
 docs/architecture.md                               |  4 +-
 packages/engine/src/__tests__/reliability-interactions/executor-liveness-gate.test.ts                 | 33 ++++++++++
 packages/engine/src/__tests__/verify-worktree-invariants-missing.test.ts     | 62 +++++++++++++++++-
 packages/engine/src/__tests__/worktree-reanchor-nested-root.test.ts          | 73 ++++++++++++++++++++++
 packages/engine/src/executor.ts                    | 54 ++++++++++++++--
 packages/engine/src/run-audit.ts                   |  1 +
 packages/engine/src/worktree-pool.ts               | 63 +++++++++++++++++++
 8 files changed, 290 insertions(+), 7 deletions(-)

Fusion-Task-Id: FN-5772

Fusion-Task-Lineage: 475de161-7bc1-4359-bb76-20c4c07196d9
This commit is contained in:
gsxdsm
2026-05-31 08:25:03 -07:00
parent 5b4eecb5d4
commit b154844286
8 changed files with 290 additions and 7 deletions

View File

@@ -103,6 +103,39 @@ describe("reliability interactions: FN-4935 executor liveness gate", () => {
expect(events.some((event) => (event.type === "worktree:incomplete-detected" || event.mutationType === "worktree:incomplete-detected") && event.metadata?.source === "executor-liveness-gate" && event.metadata?.terminalAction === "requeue-todo")).toBe(true);
});
it("re-anchors nested subdir classification failures instead of requeueing", async () => {
vi.spyOn(worktreeAcquisition, "acquireTaskWorktree").mockResolvedValue({
worktreePath: "/repo/.worktrees/gentle-flame/packages/core",
branch: "fusion/fn-4935-t",
source: "existing",
hydrated: true,
isResume: true,
});
vi.spyOn(worktreePool, "classifyTaskWorktree").mockResolvedValue({ ok: false, classification: "incomplete", reason: "missing .git metadata" });
const reanchorSpy = vi.spyOn(worktreePool, "detectNestedWorktreeRoot").mockResolvedValue({ reanchored: true, root: "/repo/.worktrees/gentle-flame" });
mockedExecSync.mockImplementation((cmd: string) => {
if (cmd.includes("rev-parse HEAD")) return Buffer.from("abc123\n");
if (cmd.includes("rev-parse --show-toplevel")) return Buffer.from("/repo/.worktrees/gentle-flame\n");
if (cmd.includes("rev-parse --abbrev-ref HEAD")) return Buffer.from("fusion/fn-4935-t\n");
if (cmd.includes("rev-list --count")) return Buffer.from("1\n");
return Buffer.from("");
});
const store = createMockStore();
const executor = new TaskExecutor(store as any, "/repo");
await executor.execute(makeTask());
expect(reanchorSpy).toHaveBeenCalled();
expect(store.updateTask).toHaveBeenCalledWith("FN-4935-T", expect.objectContaining({ worktree: "/repo/.worktrees/gentle-flame" }));
expect(store.logEntry).toHaveBeenCalledWith("FN-4935-T", expect.stringContaining("Re-anchored nested task.worktree"), undefined, expect.anything());
expect(
store.logEntry.mock.calls.some(
(call: unknown[]) => call[0] === "FN-4935-T" && typeof call[1] === "string" && call[1].includes("not_usable_task_worktree"),
),
).toBe(false);
expect(mockedCreateFnAgent).toHaveBeenCalled();
});
it.each([
"missing",
"incomplete",

View File

@@ -1,7 +1,7 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import "./executor-test-helpers.js";
import { TaskExecutor } from "../executor.js";
import { createMockStore, mockedExistsSync, resetExecutorMocks } from "./executor-test-helpers.js";
import { createMockStore, mockedExecSync, mockedExistsSync, resetExecutorMocks } from "./executor-test-helpers.js";
describe("FN-009: verifyWorktreeInvariants with missing worktree directory", () => {
let executor: TaskExecutor;
@@ -70,6 +70,66 @@ describe("FN-009: verifyWorktreeInvariants with missing worktree directory", ()
expect(result).toBeDefined();
});
it("re-anchors nested task worktree to registered root and passes invariants", async () => {
const task = {
id: "FN-9004",
title: "Test",
description: "Test",
column: "in-progress",
worktree: "/repo/.worktrees/gentle-flame/packages/core",
branch: "fusion/fn-9004",
dependencies: [],
steps: [],
currentStep: 0,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
mockedExistsSync.mockReturnValue(true);
mockedExecSync.mockImplementation((cmd: string) => {
if (cmd.includes("rev-parse --show-toplevel")) return Buffer.from("/repo/.worktrees/gentle-flame\n");
if (cmd.includes("worktree list --porcelain")) {
return Buffer.from("worktree /repo\nbranch refs/heads/main\n\nworktree /repo/.worktrees/gentle-flame\nbranch refs/heads/fusion/fn-9004\n");
}
if (cmd.includes("rev-parse --abbrev-ref HEAD")) return Buffer.from("fusion/fn-9004\n");
if (cmd.includes("rev-list --count")) return Buffer.from("1\n");
return Buffer.from("");
});
const result = await (executor as any).verifyWorktreeInvariants(task);
expect(result).toEqual({ ok: true });
expect(store.updateTask).toHaveBeenCalledWith("FN-9004", { worktree: "/repo/.worktrees/gentle-flame" });
});
it("preserves wrong_toplevel for non-reanchorable mismatch", async () => {
const task = {
id: "FN-9005",
title: "Test",
description: "Test",
column: "in-progress",
worktree: "/repo/.worktrees/gentle-flame/packages/core",
branch: "fusion/fn-9005",
dependencies: [],
steps: [],
currentStep: 0,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
mockedExistsSync.mockReturnValue(true);
mockedExecSync.mockImplementation((cmd: string) => {
if (cmd.includes("rev-parse --show-toplevel")) return Buffer.from("/repo\n");
return Buffer.from("");
});
const result = await (executor as any).verifyWorktreeInvariants(task);
expect(result.ok).toBe(false);
expect(result.reason).toBe("wrong_toplevel");
expect(store.updateTask).not.toHaveBeenCalledWith("FN-9005", expect.objectContaining({ worktree: expect.any(String) }));
});
it("preserves validation failure when worktree path is null", async () => {
const task = {
id: "FN-9003",

View File

@@ -0,0 +1,73 @@
import { beforeEach, describe, expect, it } from "vitest";
import "./executor-test-helpers.js";
import { detectNestedWorktreeRoot } from "../worktree-pool.js";
import { mockedExecSync, mockedExistsSync, resetExecutorMocks } from "./executor-test-helpers.js";
describe("detectNestedWorktreeRoot", () => {
beforeEach(() => {
resetExecutorMocks();
mockedExistsSync.mockReturnValue(true);
});
it("re-anchors when task worktree is a nested subdirectory of a registered worktree root", async () => {
mockedExecSync.mockImplementation((cmd: string) => {
if (cmd.includes("rev-parse --show-toplevel")) return Buffer.from("/repo/.worktrees/gentle-flame\n");
if (cmd.includes("worktree list --porcelain")) {
return Buffer.from("worktree /repo\nbranch refs/heads/main\n\nworktree /repo/.worktrees/gentle-flame\nbranch refs/heads/fusion/fn-1\n");
}
return Buffer.from("");
});
const result = await detectNestedWorktreeRoot("/repo", "/repo/.worktrees/gentle-flame/packages/core");
expect(result).toEqual({ reanchored: true, root: "/repo/.worktrees/gentle-flame" });
});
it("does not re-anchor when git top-level is repo root", async () => {
mockedExecSync.mockImplementation((cmd: string) => {
if (cmd.includes("rev-parse --show-toplevel")) return Buffer.from("/repo\n");
return Buffer.from("");
});
const result = await detectNestedWorktreeRoot("/repo", "/repo/.worktrees/gentle-flame/packages/core");
expect(result).toEqual({ reanchored: false, reason: "toplevel_is_repo_root" });
});
it("does not re-anchor when top-level is outside configured worktrees dir", async () => {
mockedExecSync.mockImplementation((cmd: string) => {
if (cmd.includes("rev-parse --show-toplevel")) return Buffer.from("/tmp/other\n");
return Buffer.from("");
});
const result = await detectNestedWorktreeRoot("/repo", "/repo/.worktrees/gentle-flame/packages/core");
expect(result).toEqual({ reanchored: false, reason: "toplevel_outside_configured_dir" });
});
it("does not re-anchor when top-level worktree is not registered", async () => {
mockedExecSync.mockImplementation((cmd: string) => {
if (cmd.includes("rev-parse --show-toplevel")) return Buffer.from("/repo/.worktrees/gentle-flame\n");
if (cmd.includes("worktree list --porcelain")) {
return Buffer.from("worktree /repo\nbranch refs/heads/main\n\nworktree /repo/.worktrees/some-other\nbranch refs/heads/fusion/fn-2\n");
}
return Buffer.from("");
});
const result = await detectNestedWorktreeRoot("/repo", "/repo/.worktrees/gentle-flame/packages/core");
expect(result).toEqual({ reanchored: false, reason: "toplevel_not_registered_worktree" });
});
it("does not re-anchor when worktree path is missing", async () => {
mockedExistsSync.mockReturnValue(false);
const result = await detectNestedWorktreeRoot("/repo", "/repo/.worktrees/gentle-flame/packages/core");
expect(result).toEqual({ reanchored: false, reason: "worktree_missing" });
});
it("does not re-anchor when worktree path is already at top-level", async () => {
mockedExecSync.mockImplementation((cmd: string) => {
if (cmd.includes("rev-parse --show-toplevel")) return Buffer.from("/repo/.worktrees/gentle-flame\n");
return Buffer.from("");
});
const result = await detectNestedWorktreeRoot("/repo", "/repo/.worktrees/gentle-flame");
expect(result).toEqual({ reanchored: false, reason: "already_at_toplevel" });
});
});

View File

@@ -46,7 +46,7 @@ import { resolveSandboxBackend } from "./sandbox/index.js";
import type { SandboxBackend } from "./sandbox/types.js";
import { ModelRegistry, SessionManager, type ToolDefinition, type AgentSession } from "@earendil-works/pi-coding-agent";
import { PRIORITY_EXECUTE, type AgentSemaphore } from "./concurrency.js";
import { RemovalReason, classifyTaskWorktree, describeRegisteredWorktrees, getRegisteredWorktreePaths, isGitRepository, isInsideWorktreesDir, isRegisteredGitWorktree, removeWorktree, type WorktreePool } from "./worktree-pool.js";
import { RemovalReason, classifyTaskWorktree, describeRegisteredWorktrees, detectNestedWorktreeRoot, getRegisteredWorktreePaths, isGitRepository, isInsideWorktreesDir, isRegisteredGitWorktree, removeWorktree, type WorktreePool } from "./worktree-pool.js";
import { attemptBranchAutocorrect } from "./branch-autocorrect.js";
import { ActiveSessionWorktreeRemovalError } from "./worktree-backend.js";
import {
@@ -3380,9 +3380,18 @@ export class TaskExecutor {
if (!livenessFailure && shouldGate) {
const classification = await classifyTaskWorktree(this.rootDir, worktreePath);
if (!classification.ok) {
livenessClassification = classification.classification;
livenessFailureReason = classification.reason;
livenessFailure = `not_usable_task_worktree:${classification.classification}`;
const reanchor = await detectNestedWorktreeRoot(this.rootDir, worktreePath, settings);
if (reanchor.reanchored) {
await this.store.updateTask(task.id, { worktree: reanchor.root });
await this.store.logEntry(task.id, `Re-anchored nested task.worktree from ${worktreePath} to ${reanchor.root}`, undefined, this.getRunContextFor(task.id));
await this.emitWorktreeReanchoredAudit(task.id, worktreePath, reanchor.root, "executor-liveness-gate");
worktreePath = reanchor.root;
observedWorktreeRealpath = canonicalizePath(reanchor.root);
} else {
livenessClassification = classification.classification;
livenessFailureReason = classification.reason;
livenessFailure = `not_usable_task_worktree:${classification.classification}`;
}
}
}
@@ -5916,6 +5925,7 @@ export class TaskExecutor {
private async verifyWorktreeInvariants(
task: Task,
worktreePathOverride?: string,
allowReanchor = true,
): Promise<{ ok: true } | { ok: false; reason: "wrong_toplevel" | "wrong_branch" | "no_commits"; observed: string; expected: string }> {
const settings = await this.store.getSettings();
const branchName = task.branch || canonicalFusionBranchName(task.id);
@@ -5972,6 +5982,16 @@ export class TaskExecutor {
!isInsideWorktreesDir(this.rootDir, observedTopLevel, settings) ||
observedTopLevel !== expectedWorktreeRealpath
) {
if (allowReanchor && observedTopLevel !== expectedRoot && isInsideWorktreesDir(this.rootDir, observedTopLevel, settings)) {
const reanchor = await detectNestedWorktreeRoot(this.rootDir, worktreePath, settings);
if (reanchor.reanchored) {
await this.store.updateTask(task.id, { worktree: reanchor.root });
executorLog.log(`${task.id}: re-anchored nested task.worktree ${worktreePath} -> ${reanchor.root}`);
await this.store.logEntry(task.id, `Re-anchored nested task.worktree from ${worktreePath} to ${reanchor.root}`, undefined, this.getRunContextFor(task.id));
await this.emitWorktreeReanchoredAudit(task.id, worktreePath, reanchor.root, "verify-worktree-invariants");
return this.verifyWorktreeInvariants(task, reanchor.root, false);
}
}
return {
ok: false,
reason: "wrong_toplevel",
@@ -9167,6 +9187,32 @@ Backward compat fallback: if JSON is unavailable, you may still begin output wit
return true;
}
private async emitWorktreeReanchoredAudit(
taskId: string,
fromPath: string,
toPath: string,
source: "verify-worktree-invariants" | "executor-liveness-gate",
): Promise<void> {
const runContext = this.getRunContextFor(taskId);
if (!runContext?.runId || !runContext.agentId) return;
const auditor = createRunAuditor(this.store, {
runId: runContext.runId,
agentId: runContext.agentId,
taskId,
phase: "execute",
});
await auditor.git({
type: "worktree:reanchored",
target: toPath,
metadata: {
taskId,
fromPath,
toPath,
source,
},
});
}
private async emitStaleLockAudit(
taskId: string,
event:

View File

@@ -93,6 +93,7 @@ export type GitMutationType =
| "worktree:remove"
| "worktree:reuse"
| "worktree:incomplete-detected"
| "worktree:reanchored"
| "worktree:auto-recovered"
/**
* worktrunk run-audit metadata shape:

View File

@@ -197,6 +197,69 @@ export type TaskWorktreeClassificationResult =
| { ok: true }
| { ok: false; classification: TaskWorktreeClassification; reason: string };
export type NestedWorktreeRootDetectionResult =
| { reanchored: true; root: string }
| { reanchored: false; reason: string };
export async function detectNestedWorktreeRoot(
rootDir: string,
worktreePath: string,
settings?: Pick<Settings, "worktreesDir">,
): Promise<NestedWorktreeRootDetectionResult> {
if (!existsSync(worktreePath)) {
return { reanchored: false, reason: "worktree_missing" };
}
if (!isInsideWorktreesDir(rootDir, worktreePath, settings)) {
return { reanchored: false, reason: "worktree_outside_configured_dir" };
}
const canonicalRootDir = canonicalizePath(rootDir);
const canonicalWorktreePath = canonicalizePath(worktreePath);
let topLevelRaw = "";
try {
const result = await execAsync("git rev-parse --show-toplevel", {
cwd: worktreePath,
encoding: "utf-8",
timeout: 10_000,
maxBuffer: 1024 * 1024,
});
topLevelRaw = getExecStdout(result).trim();
} catch (error) {
return { reanchored: false, reason: `top_level_probe_failed:${error instanceof Error ? error.message : String(error)}` };
}
if (!topLevelRaw) {
return { reanchored: false, reason: "top_level_empty" };
}
const canonicalTopLevel = canonicalizePath(topLevelRaw);
if (canonicalTopLevel === canonicalWorktreePath) {
return { reanchored: false, reason: "already_at_toplevel" };
}
if (canonicalTopLevel === canonicalRootDir) {
return { reanchored: false, reason: "toplevel_is_repo_root" };
}
if (!isInsideWorktreesDir(rootDir, canonicalTopLevel, settings)) {
return { reanchored: false, reason: "toplevel_outside_configured_dir" };
}
const relFromTopLevel = relative(canonicalTopLevel, canonicalWorktreePath);
const nestedUnderTopLevel = relFromTopLevel !== "" && !relFromTopLevel.startsWith("..") && !isAbsolute(relFromTopLevel);
if (!nestedUnderTopLevel) {
return { reanchored: false, reason: "not_nested_under_toplevel" };
}
if (!await isRegisteredGitWorktree(rootDir, canonicalTopLevel)) {
return { reanchored: false, reason: "toplevel_not_registered_worktree" };
}
return { reanchored: true, root: canonicalTopLevel };
}
/**
* Language-agnostic liveness/classification gate for task worktrees.
*/