From e5b6a6a1b93975d3053ca67567180f99bcef8531 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Apr 2026 15:11:12 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix(worktree):=20resolve=20merge=20conflict?= =?UTF-8?q?=20for=20PR=20#3322=20=E2=80=94=20adopt=20comprehensive=20pre-m?= =?UTF-8?q?erge=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Main already had a simpler step 7c (removing only MERGE_HEAD). The PR's step 7b is more thorough: it also removes SQUASH_MSG and MERGE_MSG, matching the existing post-merge cleanup pattern. Replace 7c with 7b. https://claude.ai/code/session_01SSHD9RNwVGNxAJZEgNZpgZ --- src/resources/extensions/gsd/auto-worktree.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/resources/extensions/gsd/auto-worktree.ts b/src/resources/extensions/gsd/auto-worktree.ts index 256ad11a3..92cb389c8 100644 --- a/src/resources/extensions/gsd/auto-worktree.ts +++ b/src/resources/extensions/gsd/auto-worktree.ts @@ -1566,14 +1566,17 @@ export function mergeMilestoneToMain( // Non-fatal — proceed with merge; untracked files may block it } - // 7c. Clean stale MERGE_HEAD before the squash merge (#2912). - // The native (libgit2) merge path or a prior interrupted merge may leave - // MERGE_HEAD in the git dir. `git merge --squash` refuses to run when - // MERGE_HEAD exists, so remove it preemptively. + // 7b. Clean up stale merge state before attempting squash merge (#2912). + // A leftover MERGE_HEAD (from a previous failed merge, libgit2 native path, + // or interrupted operation) causes `git merge --squash` to refuse with + // "fatal: You have not concluded your merge (MERGE_HEAD exists)". + // Defensively remove merge artifacts before starting. try { - const gitDirPre = resolveGitDir(originalBasePath_); - const mergeHeadPre = join(gitDirPre, "MERGE_HEAD"); - if (existsSync(mergeHeadPre)) unlinkSync(mergeHeadPre); + const gitDir_ = resolveGitDir(originalBasePath_); + for (const f of ["SQUASH_MSG", "MERGE_MSG", "MERGE_HEAD"]) { + const p = join(gitDir_, f); + if (existsSync(p)) unlinkSync(p); + } } catch { /* best-effort */ } // 8. Squash merge — auto-resolve .gsd/ state file conflicts (#530) From 1edf17246344709270fd38f0adf7576fa8898e4f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Apr 2026 15:19:18 +0000 Subject: [PATCH 2/2] test(worktree): add regression test for SQUASH_MSG/MERGE_MSG pre-merge cleanup (#2912) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Satisfies CI require-tests gate by adding a test that verifies the comprehensive pre-merge cleanup (step 7b) removes stale SQUASH_MSG and MERGE_MSG files — the enhancement over the prior MERGE_HEAD-only cleanup. https://claude.ai/code/session_01SSHD9RNwVGNxAJZEgNZpgZ --- .../auto-worktree-milestone-merge.test.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts b/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts index 826d65501..48f5897d9 100644 --- a/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +++ b/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts @@ -739,6 +739,39 @@ describe("auto-worktree-milestone-merge", { timeout: 300_000 }, () => { ); }); + test("#2912: stale SQUASH_MSG and MERGE_MSG are cleaned before squash merge", () => { + // Verifies that the pre-merge cleanup (step 7b) removes all three merge + // artifacts — not just MERGE_HEAD — so that `git merge --squash` never + // encounters leftover state from a prior interrupted operation. + const repo = freshRepo(); + const wtPath = createAutoWorktree(repo, "M294"); + + addSliceToMilestone(repo, wtPath, "M294", "S01", "Feature C", [ + { file: "feature-c.ts", content: "export const c = true;\n", message: "add feature c" }, + ]); + + const roadmap = makeRoadmap("M294", "Stale merge artifacts", [ + { id: "S01", title: "Feature C" }, + ]); + + // Plant stale merge artifacts in the git dir to simulate a prior + // interrupted merge. The pre-merge cleanup must remove all of them. + const gitDir = join(repo, ".git"); + writeFileSync(join(gitDir, "SQUASH_MSG"), "stale squash message\n"); + writeFileSync(join(gitDir, "MERGE_MSG"), "stale merge message\n"); + + mergeMilestoneToMain(repo, "M294", roadmap); + + assert.ok( + !existsSync(join(gitDir, "SQUASH_MSG")), + "#2912: stale SQUASH_MSG must be removed by pre-merge cleanup", + ); + assert.ok( + !existsSync(join(gitDir, "MERGE_MSG")), + "#2912: stale MERGE_MSG must be removed by pre-merge cleanup", + ); + }); + test("#1906: codeFilesChanged=true when real code is merged", () => { const repo = freshRepo(); const wtPath = createAutoWorktree(repo, "M190");