[BUG] Desktop: worktree sessions read hooks, skills and commands from the main checkout

Status Open
Reported on v2.1.293
Maintainer reply None cached
Activity 1 comment · opened Oct 8, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Claude Desktop 2.26454.2 (Linux, .deb, bundling Claude Code 2.1.293).

A Code-tab session that runs in a git worktree is started with the working directory set to the worktree and --project-config-root <main checkout>. Project configuration then comes from the main checkout, although the worktree has its own .claude/ files:

Hooks from .claude/settings.json run with ${CLAUDE_PROJECT_DIR} and their working directory both set to the main checkout, and the main checkout's script runs (measured: HOOK-FROM=main CLAUDE_PROJECT_DIR=<main> PWD=<main> although the committed worktree script says HOOK-FROM=worktree).
The session lists the skills of both directories. For a skill that exists in both, the main checkout's copy is used (measured: /where prints SKILL-FROM=main, the committed worktree copy says SKILL-FROM=worktree).
In our real project the text of a slash command (.claude/commands/*.md) also came from the main checkout's older copy.
Before the app update on 2026-10-07 13:20 the worktree's files were used (session transcripts: the expanded text of a slash command followed the worktree file until 2026-10-06 15:29 UTC; since 2026-10-07 14:36 UTC it is the main checkout's text). The flag is not in claude --help, the changelog or the docs. The worktrees docs ("What worktrees share with the main checkout") say the worktree's own .claude/skills|commands|agents load when present, and the settings docs say .claude/settings.json is read from the session's primary working directory.

This is the opposite of anthropics/claude-code#36360, anthropics/claude-code#36656 and anthropics/claude-code#45054 (there .claude/ was resolved against the worktree and not found). Related: anthropics/claude-code#29716 (Desktop, worktree hooks).

First time the issue was spotte is with Claude Desktop 2.26454.0 with Claude Code 2.1.289.

What Should Happen?

In a worktree session started by the Code tab, project configuration should load from the session's working directory (the worktree), as the docs describe:

.claude/skills/, .claude/commands/ and .claude/agents/* load from the worktree when it has its own copy (worktrees page, "What worktrees share with the main checkout"). The main checkout's copy should only be used as a fallback when the worktree has none.
.claude/settings.json (including hooks) is read from the session's primary working directory (settings page), and ${CLAUDE_PROJECT_DIR} in hook commands should point to the worktree the session started in.
If reading project configuration from the main checkout is intended for Desktop worktree sessions, it should be documented, and there should be a setting to use the worktree's files instead. Otherwise --project-config-root should not point at the main checkout for sessions that start inside a worktree.

Error Messages/Logs

Steps to Reproduce

Run the script below. It creates ~/repro-pcr: the committed state has marker HOOK-FROM=worktree and skills where and onlywt. The main checkout additionally has uncommitted changes (marker HOOK-FROM=main, skill onlymain). A new worktree receives the committed state only.
In the Code tab, open ~/repro-pcr, start a new session with the worktree option (branch main), and send any prompt. The prompt triggers the UserPromptSubmit hook.
cat /tmp/repro-hook.log, then type /where.
On Linux: tr '\0' '\n' < /proc/<claude pid>/cmdline | grep -A1 -x -- --project-config-root. The process whose working directory is <repo>/.claude/worktrees/<name> shows the flag.

Expected: HOOK-FROM=worktree, CLAUDE_PROJECT_DIR and PWD in the worktree, /where prints SKILL-FROM=worktree.
Actual: HOOK-FROM=main CLAUDE_PROJECT_DIR=<repo> PWD=<repo>, /where prints SKILL-FROM=main, and the process carries --project-config-root <repo>.

Without Desktop, the same effect with the flag on the command line: cd <worktree> && claude -p --setting-sources=user,project,local --project-config-root <main> --output-format stream-json --verbose "hi". The hook log shows HOOK-FROM=main with PWD=<main>.

Claude Model

Sonnet (default)

Is this a regression?

Yes, this worked in a previous version

Last Working Version

2.1.284

Claude Code Version

2.1.293

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

bash <<'ALLES'
set -e
REPRO="$HOME/repro-pcr"
rm -rf "$REPRO"
mkdir -p "$REPRO/.claude/hooks" "$REPRO/.claude/skills/where" "$REPRO/.claude/skills/onlywt"
cd "$REPRO"
git init -q -b main
cat > .claude/settings.json <<'EOS'
{
"hooks": {
"UserPromptSubmit": [
{ "hooks": [ { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}/.claude/hooks/where.sh\"" } ] }
]
}
}
EOS
cat > .claude/hooks/where.sh <<'EOS'
#!/bin/bash
echo "HOOK-FROM=worktree CLAUDE_PROJECT_DIR=$CLAUDE_PROJECT_DIR PWD=$PWD" >> /tmp/repro-hook.log
EOS
printf -- '---\nname: where\ndescription: Prints where this skill comes from\n---\nReply with exactly: SKILL-FROM=worktree\n' > .claude/skills/where/SKILL.md
printf -- '---\nname: onlywt\ndescription: Exists only in the committed files, so only in the worktree\n---\nx\n' > .claude/skills/onlywt/SKILL.md
git add -A
git -c user.email=repro@example.invalid -c user.name=repro commit -qm "committed state: this is what a new worktree receives"
sed -i 's/HOOK-FROM=worktree/HOOK-FROM=main/' .claude/hooks/where.sh
printf -- '---\nname: where\ndescription: Prints where this skill comes from\n---\nReply with exactly: SKILL-FROM=main\n' > .claude/skills/where/SKILL.md
mkdir -p .claude/skills/onlymain
printf -- '---\nname: onlymain\ndescription: Exists only as an uncommitted file in the main checkout\n---\nx\n' > .claude/skills/onlymain/SKILL.md
rm -f /tmp/repro-hook.log
echo "Fertig: $REPRO"
ALLES

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗