[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)

Status Open
Reported on v2.1.241
Maintainer reply None cached
Activity 1 comment · opened Aug 23, 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?

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. Write to a path in the launch checkout was refused with This 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):

  1. Skill(skill: "probe-baseline") → launches. Control: project skills work.
  2. EnterWorktree(path: "/tmp/skillreg-probe/wt")Entered worktree at … on branch probe-branch. The session is now working in the worktree.
  3. Bash: pwd; ls .claude/skills → prints the worktree path and lists both probe-baseline and probe-worktree-only.
  4. Skill(skill: "probe-worktree-only")Unknown skill: probe-worktree-only.
  5. Wait 150s (background sleep), then repeat step 4 → still Unknown skill. A third call ~300s after entering the worktree fails identically.
  6. Create $P/.claude/skills/probe-late-origin/SKILL.md (in the launch checkout) and wait ~90s → an "available skills" reminder announces probe-late-origin, and Skill(skill: "probe-late-origin") launches.
  7. Create a skill under $P/wt/.claude/skills/ (the worktree) and wait → never announced, Unknown skill on 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 --worktree not copying .claude/ content into the worktree. Here there is nothing to copy — the SKILL.md is committed, so it is already in the worktree, verified by ls from 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 after EnterWorktree, 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 PreToolUse hooks 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.

View original on GitHub ↗

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