From f161069cd4f404d5eca02c751bb12a34ff0d3763 Mon Sep 17 00:00:00 2001 From: Paul Gebheim <86010+pgebheim@users.noreply.github.com> Date: Wed, 5 Aug 2026 02:08:43 +0000 Subject: [PATCH] fix(rig-worktree): default worktrees to .claude/worktrees so the harness can manage them (#61) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rig created worktrees under .claude/rig-worktrees/, but the Claude Code harness only adopts worktrees under .claude/worktrees/ — so EnterWorktree / ExitWorktree refused them and you lost the native worktree lifecycle. Point the default there instead; a worktree created by setup-worktree.sh is now adoptable via EnterWorktree({ path }). --path still overrides, and remove-worktree.sh resolves via `git worktree list`, so pre-existing .claude/rig-worktrees/ worktrees still tear down fine. Two adjacent bugs found while verifying end-to-end: - remove-worktree.sh reported "(kept branch)" after force-deleting the branch: KEEP_BRANCH is "0" (non-empty) when the flag is absent, so ${KEEP_BRANCH:+…} always fired. Compare the value; name the deleted branch. - setup-worktree.sh aborted on any repo with no gitignored .env files: the env-symlink filter chain ends in a grep that exits 1 on no match, and under `set -o pipefail` that killed the script before it printed the path or installed deps. Guard the chain with `|| true`; route `git worktree add` stdout chatter to stderr so stdout carries only the path (the `| tail -1` contract). Verified: create → lands under .claude/worktrees/, registered in `git worktree list`, populated checkout; remove (default) deletes the branch and reports it; remove --keep-branch keeps it and reports that. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_0157AoJ6PVpKyt5V8GCVrodf --- scripts/remove-worktree.sh | 10 ++++++++-- scripts/setup-worktree.sh | 33 +++++++++++++++++++++------------ skills/rig-worktree/SKILL.md | 8 +++++--- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/scripts/remove-worktree.sh b/scripts/remove-worktree.sh index 784cce4..9464220 100755 --- a/scripts/remove-worktree.sh +++ b/scripts/remove-worktree.sh @@ -8,7 +8,7 @@ # Usage: # remove-worktree.sh [options] # -# Worktree path (e.g. .claude/rig-worktrees/feat-521-foo) or +# Worktree path (e.g. .claude/worktrees/feat-521-foo) or # the branch checked out in it (e.g. alice/feat-521-foo). # # Options: @@ -97,4 +97,10 @@ fi git -C "$MAIN" worktree prune >&2 echo "removed $WT_PATH" -echo "remove-worktree: removed $WT_PATH${KEEP_BRANCH:+ (kept branch)}" >&2 +# KEEP_BRANCH is "0" when unset — non-empty, so ${KEEP_BRANCH:+…} fires either +# way. Compare it, or the script claims it kept a branch it just force-deleted. +if [ "$KEEP_BRANCH" = "1" ]; then + echo "remove-worktree: removed $WT_PATH (kept branch)" >&2 +else + echo "remove-worktree: removed $WT_PATH${BRANCH:+ (deleted branch $BRANCH)}" >&2 +fi diff --git a/scripts/setup-worktree.sh b/scripts/setup-worktree.sh index 81edaf1..e9a82c3 100755 --- a/scripts/setup-worktree.sh +++ b/scripts/setup-worktree.sh @@ -19,7 +19,7 @@ # e.g. from vcs.baseRef in .rig/config.json). # Pass origin/ for stacked children. # --path Worktree path. Default: -# .claude/rig-worktrees/. +# .claude/worktrees/. # --reuse If the worktree (or branch) already exists, reuse it: # fetch + hard-reset to instead of failing. # Without this, an existing path/branch is an error. @@ -80,9 +80,12 @@ done MAIN=$(git worktree list --porcelain | awk '/^worktree / && !seen { print $2; seen=1 }') [ -n "$MAIN" ] || die "could not resolve main worktree root" -# Default worktree path: .claude/rig-worktrees/. +# Default worktree path: .claude/worktrees/. This is the +# directory the Claude Code harness manages, so a worktree created here can be +# adopted natively via EnterWorktree/ExitWorktree (a sibling .claude/rig-worktrees/ +# path could not). Override with --path. if [ -z "$WT_PATH" ]; then - WT_PATH="$MAIN/.claude/rig-worktrees/${BRANCH##*/}" + WT_PATH="$MAIN/.claude/worktrees/${BRANCH##*/}" fi # Normalize to absolute. case "$WT_PATH" in @@ -125,10 +128,12 @@ if [ -d "$WT_PATH" ]; then else # Create the worktree. -B so an already-existing local branch is reset to # base rather than erroring; this matches a stacked integration-branch flow. + # git worktree add prints "HEAD is now at …" to stdout; route it to stderr so + # stdout carries only the final path line (the caller's `| tail -1` contract). if [ "$REUSE" = "1" ]; then - git -C "$MAIN" worktree add -B "$BRANCH" "$WT_PATH" "$BASE" + git -C "$MAIN" worktree add -B "$BRANCH" "$WT_PATH" "$BASE" >&2 else - git -C "$MAIN" worktree add -b "$BRANCH" "$WT_PATH" "$BASE" + git -C "$MAIN" worktree add -b "$BRANCH" "$WT_PATH" "$BASE" >&2 fi fi @@ -137,13 +142,17 @@ fi # missing secrets look like flaky/timeout test failures, not "config not found". # Symlinks (not copies) so edits in MAIN propagate; ln -sfn is idempotent. echo "setup-worktree: symlinking env files..." >&2 -( - cd "$MAIN" - git ls-files --others --ignored --exclude-standard -) | grep -v '/node_modules/' | grep -v '^node_modules/' \ - | grep -v '\.example$' \ - | grep -E '(^|/)\.env(\.[^/]+)?$' \ - | while read -r f; do +# `|| true` guards the whole filter chain: any grep stage that selects no lines +# exits 1, and under `set -o pipefail` that would abort the script — but "this +# repo has no .env files to link" is a normal outcome, not a failure. +{ + ( + cd "$MAIN" + git ls-files --others --ignored --exclude-standard + ) | grep -v '/node_modules/' | grep -v '^node_modules/' \ + | grep -v '\.example$' \ + | grep -E '(^|/)\.env(\.[^/]+)?$' || true +} | while read -r f; do mkdir -p "$WT_PATH/$(dirname "$f")" ln -sfn "$MAIN/$f" "$WT_PATH/$f" done diff --git a/skills/rig-worktree/SKILL.md b/skills/rig-worktree/SKILL.md index 6ce1550..fc361e8 100644 --- a/skills/rig-worktree/SKILL.md +++ b/skills/rig-worktree/SKILL.md @@ -7,7 +7,7 @@ allowed-tools: [Bash, Read] # Worktree — isolated dev checkout lifecycle -Create / list / remove isolated git worktrees under `.claude/rig-worktrees/`. +Create / list / remove isolated git worktrees under `.claude/worktrees/`. Creation produces a worktree that's actually ready to run: fetched from origin, branched from the right base, env files symlinked, dependencies installed. This is a shared bootstrap you can call directly (spikes, @@ -59,8 +59,10 @@ only required argument. - `--base ` — base to branch from. Default `vcs.baseRef`. For a stacked/integration child, pass `origin/`. - `--path ` — worktree location. Default - `.claude/rig-worktrees/` (so `alice/feat-521-foo` - → `.claude/rig-worktrees/feat-521-foo`). + `.claude/worktrees/` (so `alice/feat-521-foo` + → `.claude/worktrees/feat-521-foo`). This is the directory the Claude Code + harness manages, so a worktree created here can be adopted natively via + `EnterWorktree`/`ExitWorktree`. - `--reuse` — if the worktree/branch already exists, fetch and hard-reset it to `` instead of failing. Use this for a "reuse a child worktree" path.