fix(FN-5279): apply reuse-mode squash to local main and guard executor-lease race

In reuse-task-worktree mode the merger detaches HEAD in the task worktree so
the squash commit lands on detached HEAD; nothing previously advanced the
project root's local integration branch, so changes never appeared on main.
Step 5c now applies the squash to projectRootDir via git merge --ff-only,
falling back to a regular merge with AI conflict resolution if main has
diverged. pushAfterMerge (when enabled) now runs from projectRootDir where
the branch was just advanced, so parsePushRemoteTarget can resolve a branch
instead of failing on the worktree's detached HEAD.

Also tightens acquireReuseHandoff: the executor-lease check above the
queue-lease acquisition was non-atomic, letting a local executor grab the
task between checks. Re-check after acquisition and release the queue
lease with a precise diagnostic instead of proceeding into a generic
failure later.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-20 14:28:19 -07:00
parent d6196a8665
commit c824810181
4 changed files with 176 additions and 5 deletions

View File

@@ -22,7 +22,7 @@ import { git, hasGit, makeReliabilityFixture } from "./_helpers.js";
const mockedCreateFnAgent = vi.mocked(createFnAgent);
describe("FN-5279 reliability interactions: merge reuse task worktree", () => {
it.skipIf(!hasGit)("happy path merges from a reused task worktree without mutating the project root", async () => {
it.skipIf(!hasGit)("happy path merges from a reused task worktree and applies the squash to the project root's integration branch", async () => {
const fixture = await makeReliabilityFixture({
taskId: "FN-5279-RI-HAPPY",
settings: {
@@ -71,7 +71,14 @@ describe("FN-5279 reliability interactions: merge reuse task worktree", () => {
const acquired = audits.find((event) => event.mutationType === "merge:reuse-handoff-acquired");
expect(acquired?.metadata).toMatchObject({ integrationRemote: "origin", integrationBranch: "master" });
expect(git(rootDir, "git rev-parse HEAD")).toBe(rootHeadBefore);
// Step 5c (FN-5279 reuse mode) advances the project root's integration
// branch to the new squash commit so changes actually land on master.
expect(auditTypes).toContain("merge:reuse-integration-branch-advanced");
const advanced = audits.find(
(event) => event.mutationType === "merge:reuse-integration-branch-advanced",
);
expect(advanced?.metadata).toMatchObject({ via: "ff-merge" });
expect(git(rootDir, "git rev-parse HEAD")).not.toBe(rootHeadBefore);
expect(git(rootDir, "git status --porcelain --untracked-files=no")).toBe(rootTrackedStatusBefore);
} finally {
await fixture.cleanup();