From 8146f72b428717e032b79d407245f9617e24c836 Mon Sep 17 00:00:00 2001 From: Terada Kousuke Date: Fri, 10 Apr 2026 00:15:39 +0900 Subject: [PATCH 1/2] fix(guardrails): align worktree creation with upstream pattern (Issue #144) Replace --detach with --detach --no-checkout + git reset --hard to reliably populate worktree working directories. On failure, cleanup broken worktrees with git worktree remove --force. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/guardrails/profile/plugins/team.ts | 33 ++++++++++++--------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/guardrails/profile/plugins/team.ts b/packages/guardrails/profile/plugins/team.ts index 7f66f7dbdddb..642b61acaf87 100644 --- a/packages/guardrails/profile/plugins/team.ts +++ b/packages/guardrails/profile/plugins/team.ts @@ -271,26 +271,31 @@ async function yardadd(dir: string, id: string) { const base = yard(dir) const next = path.join(base, slug(id)) await mkdir(base, { recursive: true }) + + // Verify repository has commits const head = await git(dir, ["rev-parse", "--verify", "HEAD"]) if (head.code !== 0) { throw new Error("Cannot create worktree: repository has no commits. Create an initial commit first.") } - const made = await git(dir, ["worktree", "add", "--detach", next, "HEAD"]) + + // Step 1: Create worktree without checking out files (upstream pattern) + const made = await git(dir, ["worktree", "add", "--detach", "--no-checkout", next, "HEAD"]) if (made.code !== 0) throw new Error(made.err || made.out || "Failed to create git worktree") - // Verify worktree actually has files (Issue #144: empty git init) - const files = await git(next, ["ls-files", "--cached"]).catch(() => ({ code: 1, out: "", err: "" })) - if (files.code !== 0 || !files.out.trim()) { - // Worktree might be empty — force checkout HEAD contents - const checkout = await git(next, ["checkout", "HEAD", "--", "."]) - if (checkout.code !== 0) { - throw new Error(`Worktree created but checkout failed: ${checkout.err || checkout.out}`) - } - // Re-verify files are present - const recheck = await git(next, ["ls-files", "--cached"]).catch(() => ({ code: 1, out: "", err: "" })) - if (recheck.code !== 0 || !recheck.out.trim()) { - throw new Error("Worktree is still empty after checkout — cannot proceed with delegation") - } + + // Step 2: Hard reset to populate working directory (upstream pattern) + const populated = await git(next, ["reset", "--hard"]) + if (populated.code !== 0) { + await git(dir, ["worktree", "remove", "--force", next]).catch(() => {}) + throw new Error(`Worktree created but population failed: ${populated.err || populated.out}`) + } + + // Step 3: Verify files are actually present in the working directory + const check = await git(next, ["ls-files", "--cached"]) + if (check.code !== 0 || !check.out.trim()) { + await git(dir, ["worktree", "remove", "--force", next]).catch(() => {}) + throw new Error("Worktree is empty after reset --hard — cannot proceed with delegation") } + return next } From 9a78bb34259e07ad4ebcc3db704c66408e4113eb Mon Sep 17 00:00:00 2001 From: Terada Kousuke Date: Fri, 10 Apr 2026 00:29:00 +0900 Subject: [PATCH 2/2] fix(guardrails): cleanup worktree on Step 1 failure (review MEDIUM-1) Add git worktree remove --force cleanup when git worktree add itself fails, preventing orphaned partial worktrees. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/guardrails/profile/plugins/team.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/guardrails/profile/plugins/team.ts b/packages/guardrails/profile/plugins/team.ts index 642b61acaf87..c47851f17aed 100644 --- a/packages/guardrails/profile/plugins/team.ts +++ b/packages/guardrails/profile/plugins/team.ts @@ -280,7 +280,10 @@ async function yardadd(dir: string, id: string) { // Step 1: Create worktree without checking out files (upstream pattern) const made = await git(dir, ["worktree", "add", "--detach", "--no-checkout", next, "HEAD"]) - if (made.code !== 0) throw new Error(made.err || made.out || "Failed to create git worktree") + if (made.code !== 0) { + await git(dir, ["worktree", "remove", "--force", next]).catch(() => {}) + throw new Error(made.err || made.out || "Failed to create git worktree") + } // Step 2: Hard reset to populate working directory (upstream pattern) const populated = await git(next, ["reset", "--hard"])