[BUG] Project-skill registry stays rooted at the launch checkout after EnterWorktree — a committed SKILL.md in the entered worktree is permanently "Unknown skill" (2.1.241)
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?
After EnterWorktree moves a session into a linked worktree, project-skill discovery stays rooted at the checkout the session launched in. A SKILL.md that is committed on the entered worktree's branch, and that the session can ls and cat from its own cwd, answers Unknown skill — permanently, for the rest of the session.
The registry is not frozen at session start, which is what makes this worth reporting separately: it is watching the wrong tree. In the same session, a skill written into the launch checkout mid-session registered within ~90s and became invocable, announced by the usual "The following skills are available for use with the Skill tool" reminder. A skill written into the entered worktree never registered at all. So a live rescan exists; its root just never follows the session.
Two more observations from the same run make the state internally inconsistent rather than merely incomplete:
- The session did genuinely re-root. A transcript directory slugged from the worktree path appeared under
~/.claude/projects/, and background-task output was written under a/tmp/claude-<uid>/<worktree-slug>/path — so this is not the partial-application case in #86020. - The session simultaneously enforced worktree isolation on writes.
Writeto a path in the launch checkout was refused withThis session is isolated in the worktree <worktree>. Edit the worktree copy of this file instead of the shared-checkout path.— while skill discovery was resolving from exactly that shared checkout. The session's two answers to "which tree am I in" disagree, and skill discovery is the half that never moved.
Practical impact: a session launched from a checkout that predates a skill can never reach that skill, and entering a worktree that has it does not help. The only remedy is to read the SKILL.md and execute its steps by hand, which loses the skill's allowed-tools scoping and context: fork.
What Should Happen?
EnterWorktree should re-root project-skill discovery at the entered worktree (or, failing that, include it), so a SKILL.md present on disk in the session's working directory is invocable. Per the docs, project skills are discovered by walking up from the current working directory — after EnterWorktree the current working directory is the worktree.
Failing a re-root, Unknown skill should say so: an error naming the roots actually searched would turn a silent dead end into a one-line diagnosis.
Error Messages/Logs
# cwd is the entered worktree; the file is right there
$ pwd
/tmp/skillreg-probe/wt
$ ls .claude/skills
probe-baseline
probe-worktree-only
# ...and yet, in that same session:
Skill(skill: "probe-worktree-only")
<tool_use_error>Unknown skill: probe-worktree-only</tool_use_error>
# 150s later, same call, same session:
<tool_use_error>Unknown skill: probe-worktree-only</tool_use_error>
# meanwhile a skill written into the LAUNCH checkout mid-session, ~90s earlier:
Skill(skill: "probe-late-origin")
Launching skill: probe-late-origin
-> LATE_ORIGIN_RAN
Steps to Reproduce
Setup — a throwaway repo whose main lacks a skill that its worktree branch has:
P=/tmp/skillreg-probe
mkdir -p "$P" && cd "$P" && git init -q -b main .
mkdir -p .claude/skills/probe-baseline
printf -- '---\nname: probe-baseline\ndescription: Control skill in the launch checkout.\n---\nOutput the exact token BASELINE_SKILL_RAN and stop.\n' \
> .claude/skills/probe-baseline/SKILL.md
echo probe > README.md && git add -A && git commit -qm base
git checkout -qb probe-branch
mkdir -p .claude/skills/probe-worktree-only
printf -- '---\nname: probe-worktree-only\ndescription: Skill that exists only on the worktree branch.\n---\nOutput the exact token WORKTREE_SKILL_RAN and stop.\n' \
> .claude/skills/probe-worktree-only/SKILL.md
git add -A && git commit -qm wt
git checkout -q main
git worktree add -q "$P/wt" probe-branch
Then, in a session launched from $P (the launch checkout):
Skill(skill: "probe-baseline")→ launches. Control: project skills work.EnterWorktree(path: "/tmp/skillreg-probe/wt")→Entered worktree at … on branch probe-branch. The session is now working in the worktree.Bash: pwd; ls .claude/skills→ prints the worktree path and lists bothprobe-baselineandprobe-worktree-only.Skill(skill: "probe-worktree-only")→Unknown skill: probe-worktree-only.- Wait 150s (background
sleep), then repeat step 4 → stillUnknown skill. A third call ~300s after entering the worktree fails identically. - Create
$P/.claude/skills/probe-late-origin/SKILL.md(in the launch checkout) and wait ~90s → an "available skills" reminder announcesprobe-late-origin, andSkill(skill: "probe-late-origin")launches. - Create a skill under
$P/wt/.claude/skills/(the worktree) and wait → never announced,Unknown skillon call.
Steps 1-6 reproduced identically in two independent sessions launched the same way. Step 6 is the positive control that a live rescan exists — it is just rooted at $P, never at $P/wt.
Claude Model
Other
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.241 (Claude Code)
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
Non-interactive/CI environment
Additional Information
Run non-interactively: claude -p "<the steps above>" --permission-mode bypassPermissions --output-format stream-json --verbose. Every quoted string above is verbatim tool_result text from that stream.
Why this looks distinct from the existing reports:
- #28041 (open) is about
claude --worktreenot copying.claude/content into the worktree. Here there is nothing to copy — theSKILL.mdis committed, so it is already in the worktree, verified bylsfrom the session's own cwd. The 2026-08-19 comment there ("a worktree is a plain git checkout, so anything committed under.claude/shows up there automatically") is true on disk and is exactly what this report is about: the file shows up, and the registry still does not read it. - #48761 and #47503 both cover skill discovery in worktrees and are stale-closed and locked; their lock notices ask that a new issue be filed referencing them, which is what this is. Neither carries the launch-checkout-rooting measurement or the live-rescan control.
- #73387 (open) is the agent-registry counterpart — project
.claude/agents/deregistering afterEnterWorktree, self-healing on a later rescan. Same shape one registry over; filed separately since it is a different area and its registry does eventually recover. - #87918 and #76897 report the same launch-directory-rooting class for markdown links and for
PreToolUsehooks respectively. - #86020 is excluded above by the transcript-directory check.
If it helps, the fix that would cover all of these at once is re-rooting whatever the rescan walks up from when the session's working directory changes, rather than per-registry.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗