feat(FN-4114): complete Step 3 — enforce pre-session worktree liveness

Fusion-Task-Id: FN-4114
Fusion-Task-Lineage: 44fd5718-42dc-44d1-8f5a-97b67dd05d13
This commit is contained in:
Fusion
2026-05-13 04:00:32 -07:00
committed by gsxdsm
parent 7048d4efc2
commit 552d9ad560

View File

@@ -36,7 +36,7 @@ import { buildSessionSkillContext } from "./session-skill-context.js";
import { reviewStep, type ReviewVerdict } from "./reviewer.js";
import { ModelRegistry, SessionManager, type ToolDefinition, type AgentSession } from "@mariozechner/pi-coding-agent";
import { PRIORITY_EXECUTE, type AgentSemaphore } from "./concurrency.js";
import { getRegisteredWorktreePaths, isGitRepository, isInsideWorktreesDir, isRegisteredGitWorktree, type WorktreePool } from "./worktree-pool.js";
import { getRegisteredWorktreePaths, isGitRepository, isInsideWorktreesDir, isRegisteredGitWorktree, isUsableTaskWorktree, type WorktreePool } from "./worktree-pool.js";
import { BranchConflictError, isBranchConflictError, inspectBranchConflict } from "./branch-conflicts.js";
import { AgentLogger } from "./agent-logger.js";
import { executorLog, reviewerLog, formatError } from "./logger.js";
@@ -2510,6 +2510,73 @@ export class TaskExecutor {
}
}
const expectedRoot = realpathSync(this.rootDir);
let observedWorktreeRealpath: string;
let livenessFailure: string | null = null;
try {
observedWorktreeRealpath = realpathSync(worktreePath);
if (observedWorktreeRealpath === expectedRoot) {
livenessFailure = "realpath_matches_repo_root";
}
} catch (error) {
observedWorktreeRealpath = `unresolvable:${worktreePath}`;
livenessFailure = `unresolvable_worktree:${error instanceof Error ? error.message : String(error)}`;
}
if (!livenessFailure && !isInsideWorktreesDir(this.rootDir, worktreePath)) {
livenessFailure = "outside_worktrees_dir";
}
if (!livenessFailure) {
const isUsable = await isUsableTaskWorktree(this.rootDir, worktreePath);
if (!isUsable) {
livenessFailure = "not_usable_task_worktree";
}
}
if (livenessFailure) {
const expected = `<repo>/.worktrees/* (usable, registered)`;
const observed = `${worktreePath} (${observedWorktreeRealpath})`;
const failureMessage = `worktree liveness assertion failed: ${livenessFailure} — observed=${observed}, expected=${expected}`;
executorLog.error(`${task.id}: ${failureMessage}`);
await this.store.logEntry(task.id, failureMessage, undefined, this.currentRunContext);
const priorRequeues = task.taskDoneRetryCount ?? 0;
const nextRequeueCount = priorRequeues + 1;
if (priorRequeues < MAX_TASK_DONE_REQUEUE_RETRIES) {
await this.store.updateTask(task.id, {
status: "failed",
error: failureMessage,
worktree: null,
branch: null,
sessionFile: null,
taskDoneRetryCount: nextRequeueCount,
});
await this.store.logEntry(
task.id,
`${failureMessage} — requeued to todo immediately (${nextRequeueCount}/${MAX_TASK_DONE_REQUEUE_RETRIES})`,
undefined,
this.currentRunContext,
);
await this.store.moveTask(task.id, "todo", { preserveProgress: true });
executorLog.log(`${task.id} worktree liveness failed — requeued to todo (${nextRequeueCount}/${MAX_TASK_DONE_REQUEUE_RETRIES})`);
} else {
await this.store.updateTask(task.id, {
status: "failed",
error: failureMessage,
worktree: null,
branch: null,
sessionFile: null,
});
await this.store.logEntry(task.id, `${failureMessage} — moved to in-review for inspection`, undefined, this.currentRunContext);
await this.persistTokenUsage(task.id);
await this.store.moveTask(task.id, "in-review");
executorLog.log(`${task.id} worktree liveness failed — moved to in-review`);
}
this.options.onError?.(task, new Error(failureMessage));
return;
}
this.activeWorktrees.set(task.id, worktreePath);
executorLog.log(`${task.id}: worktree ready at ${worktreePath}`);