Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ libp
linter
localhost
MacOS
macOS
md
mdBook
middleware
Expand Down Expand Up @@ -130,3 +131,7 @@ Callouts
nav
dev
reviewable
worktree
worktrees
Worktrees
anthropics
50 changes: 50 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,56 @@ Effective code reviews question "why" architectural decisions exist:
- Follow test-driven development principles when possible
- Use debugging tools like `tracing` and metrics to understand system behavior

## Parallel Development with Git Worktrees

Git worktrees allow running multiple Claude Code sessions in parallel on different branches of the same repo. However, worktrees share the same `.git` directory, which causes Claude Code's task list (`TaskCreate`/`TodoWrite`) to leak across sessions (see [anthropics/claude-code#24754](https://github.com/anthropics/claude-code/issues/24754)).

### Creating a New Worktree

The script handles worktree creation, fetching, and launching in one command:

```bash
# Create worktree from upstream/unstable (default) and launch
./scripts/claude-worktree.sh new feat/my-feature

# Create worktree from a specific base
./scripts/claude-worktree.sh new fix/bug-123 origin/stable
```

This creates `../anchor-my-feature/`, fetches the base ref, creates the branch, and launches Claude Code with an isolated task list.

**Note**: Examples use `upstream` as the remote pointing to `sigp/anchor` and `origin` as your fork. Set up with:

```bash
git remote add upstream https://github.com/sigp/anchor.git
```

### Launching in an Existing Worktree

From within any worktree, run the script instead of `claude` directly:

```bash
./scripts/claude-worktree.sh # interactive session
./scripts/claude-worktree.sh --resume # with claude args
```

### Cleanup

```bash
# Remove a worktree when done
git worktree remove ../anchor-my-feature
```

### Troubleshooting

**Script not executable**: Run `chmod +x scripts/claude-worktree.sh`

**Hash command not found**: The script requires `sha256sum` (Linux) or `shasum` (macOS). Install the appropriate tool for your platform.

**Task lists still shared**: Ensure you're launching through the script, not directly via the `claude` command.

**Different task list after moving a worktree**: The task list ID includes a hash of the absolute path. Moving the worktree directory changes the hash, resulting in a new task list.

## Session Learning Updates

After successful Claude Code sessions where the user is satisfied with results, update both CLAUDE.md and relevant specialized agents with general principles learned:
Expand Down
98 changes: 98 additions & 0 deletions scripts/claude-worktree.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env bash
# Launch Claude Code with worktree-isolated task lists.
#
# Creates a new git worktree (if needed) and launches Claude Code with
# CLAUDE_CODE_TASK_LIST_ID set so each worktree gets its own task list.
#
# Workaround for: https://github.com/anthropics/claude-code/issues/24754
#
# Usage:
# ./scripts/claude-worktree.sh new <branch> [base] Create worktree and launch
# ./scripts/claude-worktree.sh [claude args...] Launch in current directory
#
# Examples:
# ./scripts/claude-worktree.sh new feat/my-feature # from upstream/unstable
# ./scripts/claude-worktree.sh new fix/bug-123 origin/stable # from specific base
# ./scripts/claude-worktree.sh # in existing worktree
# ./scripts/claude-worktree.sh --resume # with claude args

set -euo pipefail

# --- Helper functions ---

hash_path() {
local path="$1"
if command -v sha256sum >/dev/null 2>&1; then
printf '%s' "${path}" | sha256sum | cut -c1-8
elif command -v shasum >/dev/null 2>&1; then
printf '%s' "${path}" | shasum -a 256 | cut -c1-8
else
echo "Error: Neither sha256sum nor shasum found in PATH" >&2
exit 1
fi
}

launch_claude() {
local worktree_path="$1"
shift

if [ -z "${worktree_path}" ]; then
echo "Error: Unable to determine working directory" >&2
exit 1
fi

local worktree_dir
worktree_dir="$(basename "${worktree_path}")"
local path_hash
path_hash="$(hash_path "${worktree_path}")"

export CLAUDE_CODE_TASK_LIST_ID="${worktree_dir}-${path_hash}"

command -v claude >/dev/null 2>&1 || {
echo "Error: 'claude' command not found in PATH" >&2
echo "Please install Claude Code or add it to your PATH" >&2
exit 1
}

echo "Task list isolated to: ${CLAUDE_CODE_TASK_LIST_ID}"
exec claude "$@"
}

# --- Main ---

if [ "${1:-}" = "new" ]; then
shift

if [ $# -lt 1 ]; then
echo "Usage: $0 new <branch-name> [base-ref]" >&2
echo " base-ref defaults to upstream/unstable (fetched automatically)" >&2
exit 1
fi

BRANCH="$1"
BASE="${2:-upstream/unstable}"

# Derive worktree directory name from branch (e.g., feat/my-feature -> anchor-my-feature)
REPO_NAME="$(basename "$(git rev-parse --show-toplevel)")"
DIR_SUFFIX="$(basename "${BRANCH}")"
WORKTREE_DIR="../${REPO_NAME}-${DIR_SUFFIX}"

# Fetch if base looks like a remote ref
if [[ "${BASE}" == */* ]]; then
REMOTE="${BASE%%/*}"
REF="${BASE#*/}"
echo "Fetching ${REF} from ${REMOTE}..."
git fetch "${REMOTE}" "${REF}" 2>&1
fi

echo "Creating worktree at ${WORKTREE_DIR} on branch ${BRANCH}..."
git worktree add "${WORKTREE_DIR}" -b "${BRANCH}" "${BASE}"

WORKTREE_PATH="$(cd "${WORKTREE_DIR}" && pwd)"
cd "${WORKTREE_PATH}"
launch_claude "${WORKTREE_PATH}"
else
# Launch in current directory
WORKTREE_PATH="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
launch_claude "${WORKTREE_PATH}" "$@"
fi
Loading