claude --worktree: .claude/ subdirectories (skills, agents, docs, rules) not copied to worktree

Status Open
Reported on v2.1.51
Maintainer reply None cached
Activity 7 comments · opened Feb 24, 2026

Bug Description

When using claude --worktree, the generated worktree's .claude/ directory only contains settings.local.json. All other subdirectories (skills/, agents/, docs/, rules/) and settings.json are missing, causing skills and custom agents to be unavailable in the worktree session.

Steps to Reproduce

  1. Set up a project with .claude/ directory containing skills, agents, docs, and rules
  2. Run claude --worktree
  3. Check the worktree's .claude/ directory

Expected Behavior

The worktree's .claude/ directory should contain (or symlink to) all subdirectories from the main repo's .claude/:

  • skills/
  • agents/
  • docs/
  • rules/
  • settings.json
  • settings.local.json

Actual Behavior

Only settings.local.json is present in the worktree's .claude/ directory:

Main repo .claude/:

.claude/
├── agents/
│   ├── code-worker.md
│   └── grafana-log-search.md
├── docs/
│   ├── coding-guidelines.md
│   └── go-patterns.md
├── rules/
│   ├── db-schema.md
│   └── sync-rag-voicealf.md
├── settings.json
├── settings.local.json
├── skills/
│   ├── cleanup-stale-/
│   ├── commit-push-pr/
│   ├── llm-test/
│   └── worktree/
└── worktrees/

Worktree .claude/:

.claude/
└── settings.local.json

Environment

  • Claude Code version: 2.1.51
  • OS: macOS (Darwin 25.2.0)
  • Shell: zsh

Suggested Fix

Either copy or symlink the skills/, agents/, docs/, rules/, and settings.json from the source project's .claude/ directory when creating the worktree's .claude/ directory.

View original on GitHub ↗

5 Comments

wangmir · 6 months ago

It's painful because when I use this option, claude keeps trying to edit from main branch rather than worktree.

echarrod · 6 months ago

We'd expect Claude to copy its own .claude/ subdirectories (skills, agents, settings, etc.) to worktrees out of the box — that seems like a bug rather than a feature request.

Beyond that, a more general solution could be to support a .worktreeinclude file (or similar) that specifies additional files and directories to copy to new worktrees. This would cover other common needs like local config files, IDE settings, and tooling dependencies.

For example:

# .worktreeinclude
.vscode/
config.local.json

This pattern is already used by tools like git-worktree-runner (via .gtrconfig) to handle the same problem. Supporting it natively in Claude Code would avoid needing a WorktreeCreate hook that replaces the entire git worktree add flow just to copy a few files.

wangmir · 6 months ago

I tried to find solution with claude, it seems we have hook for worktree create / remove. And it seems overriding existing worktree features at all. (But I couldn't find same info from official doc, so I'm not sure. but when I test it, it was working)

So, my setup is currently like this. I tried to even separate worktree directory from .claude because I know we can remove the directory from IDE, and also can .gitignore the directory too, but it was a bit annoying to have worktree inside of .claude directory inside of my project.

1. Hook setting

"WorktreeCreate": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash $HOME/.claude/hooks/setup-worktree.sh"
          }
        ]
      }
    ],
    "WorktreeRemove": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash $HOME/.claude/hooks/cleanup-worktree.sh"
          }
        ]
      }
    ]

setup-worktree.sh

#!/bin/bash
set -euo pipefail

# =============================================================================
# Claude Code WorktreeCreate Hook
# =============================================================================
# Creates git worktrees at ~/claude-worktrees/<repo>/<name> with .claude and
# .serena configs copied from the original project.
#
# Supports 3 scenarios:
#   1. New worktree       - creates worktree + new branch from default branch
#   2. Re-entry           - worktree directory still exists, just returns path
#   3. Orphaned branch    - directory was removed but branch remains, reuses it
#
# Hook input (JSON via stdin):
#   { "name": "<worktree-name>", "cwd": "<project-root>" }
#
# Hook output (stdout):
#   Absolute path to the worktree directory (consumed by Claude Code)
# =============================================================================

# Read hook input from stdin
INPUT=$(cat)
NAME=$(echo "$INPUT" | jq -r '.name')
CWD=$(echo "$INPUT" | jq -r '.cwd')

# Derive repo name from git remote or directory name
REPO_NAME=$(git -C "$CWD" remote get-url origin 2>/dev/null | sed 's|.*/||; s|\.git$||' || basename "$CWD")

# --- Worktree location ---
# Change WORKTREE_BASE to customize where worktrees are stored.
# Default: ~/claude-worktrees/<repo>/<name>
WORKTREE_BASE="$HOME/claude-worktrees/$REPO_NAME"
WORKTREE_PATH="$WORKTREE_BASE/$NAME"

# --- Re-entry: worktree already exists, skip creation ---
# When a worktree is "kept" on session exit and the same name is used again,
# we detect the existing .git file (git worktrees use a .git *file*, not dir)
# and return the path immediately without re-creating.
if [ -d "$WORKTREE_PATH/.git" ] || [ -f "$WORKTREE_PATH/.git" ]; then
    echo "Re-entering existing worktree: $WORKTREE_PATH" >&2
    echo "$WORKTREE_PATH"
    exit 0
fi

mkdir -p "$WORKTREE_BASE"

# --- Base branch for new worktrees ---
# By default, new worktrees branch off the remote default branch (e.g. origin/main).
# To branch from the current HEAD instead, replace the line below with:
#   BASE_REF="HEAD"
# To branch from a specific branch:
#   BASE_REF="origin/develop"
DEFAULT_BRANCH=$(git -C "$CWD" symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||' || echo "main")
BASE_REF="origin/$DEFAULT_BRANCH"

# --- Create worktree ---
# If a branch named "worktree-<name>" already exists (e.g. directory was removed
# but branch was kept), reuse it. Otherwise create a new branch from BASE_REF.
BRANCH_NAME="worktree-$NAME"
if git -C "$CWD" show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
    # Orphaned branch: directory gone but branch remains - reattach it
    git -C "$CWD" worktree add "$WORKTREE_PATH" "$BRANCH_NAME" >&2
else
    # Fresh start: new branch from BASE_REF
    git -C "$CWD" worktree add "$WORKTREE_PATH" -b "$BRANCH_NAME" "$BASE_REF" >&2
fi

# --- Copy project configs into worktree ---
# .claude settings (exclude worktrees directory to avoid recursion)
if [ -d "$CWD/.claude" ]; then
    mkdir -p "$WORKTREE_PATH/.claude"
    for f in "$CWD/.claude"/*; do
        base=$(basename "$f")
        [ "$base" = "worktrees" ] && continue
        cp -r "$f" "$WORKTREE_PATH/.claude/" 2>/dev/null || true
    done
fi

# .serena config (LSP/semantic tools)
if [ -d "$CWD/.serena" ]; then
    cp -r "$CWD/.serena" "$WORKTREE_PATH/.serena"
fi

# Output the worktree path (this is what Claude Code uses)
echo "$WORKTREE_PATH"

3. cleanup-worktree.sh

#!/bin/bash
set -euo pipefail

# Read hook input from stdin
INPUT=$(cat)
WORKTREE_PATH=$(echo "$INPUT" | jq -r '.worktree_path')

if [ -z "$WORKTREE_PATH" ] || [ "$WORKTREE_PATH" = "null" ]; then
    echo "No worktree_path provided" >&2
    exit 0
fi

# Remove the git worktree
git worktree remove "$WORKTREE_PATH" --force 2>/dev/null || true

# Clean up empty parent directories
PARENT=$(dirname "$WORKTREE_PATH")
rmdir "$PARENT" 2>/dev/null || true
EgorDuplensky · 6 months ago

I think the behavior should be even more generic.
By default, when claude-code detects that is run from a worktree it should just fetch all the settings from the main repo, without creating any symlinks.
But one should be able to add custom setting to the current worktree which will override the setting of the super repo, like it is done with git config and worktrees
Basically, git worktree behavior should be applied to claude worktrees.

Btw, even claude-code itself considers it as a bug :)

UPD: I have also noticed that when I use symlink fo .claude from a worktree, claude-code often still tries to search in a base repo even I have extra instruction in CLAUDE.md to always work within current git worktree if exists

houserooms · 5 months ago

Production impact report — .claude/rules/ not available in worktrees

We run Claude Code with EnterWorktree on every session (~8-10 hours/day, business operations). .claude/rules/ contains our project instructions — logging rules, communication rules, session procedures, etc. These files are critical: without them, Claude doesn't know how to use our database, draft emails, or follow our workflows.

What happens: When a worktree is created, .claude/rules/ is either missing or contains stale copies. If we update rules in the canonical repo, no worktree picks up the changes. On 25 March we did a database schema migration that required rule file updates — every subsequent session loaded stale rules and couldn't work with the new schema until we manually copied files.

Time wasted: ~30 minutes diagnosing, plus ongoing confusion in every new session.

Our workaround: We added !.claude/rules/ to .gitignore so rules are git-tracked and propagate via worktree creation. This works for rules but doesn't help other .claude/ contents (scripts, hooks, settings).

Suggested fix: At worktree creation time, copy (or symlink) .claude/rules/, .claude/settings.json, and .claude/scripts/ from the main repo into the new worktree. These are project-level config, not session state — they should always be current. A simple cp -r of these specific subdirectories during EnterWorktree would solve it completely.

Showing cached comments. Read the full discussion on GitHub ↗