anthropics / anthropics/claude-code

EnterWorktree(path=) rejects valid worktrees when .git is a symlink — gitdir realpath mismatch vs string join (regression in 2.1.207)

Ouverte
#89,181 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub
area:core bug has repro platform:macos regression
Langage dominant
Python
Étoiles
145k
Forks
23.1k
Métriques de merge des PR
Métriques de PR en attente

Description

## Summary

`EnterWorktree` with `path=` rejects every valid, Claude-created worktree with
`Cannot enter worktree: is not a linked worktree of ` when the repository's `.git` is a
**symlink** (e.g. `.git → .git.nosync`, the common macOS pattern for keeping git internals out of
iCloud Drive sync under `~/Documents`). Creating worktrees (`name=`) works fine; only re-entering an
existing one is broken.

## Regression window (bisected from session transcripts)

- **≤ 2.1.206**: 88 `EnterWorktree` calls across these repos, 0 failures.
- **2.1.207** (first ran 2026-07-13T09:47Z): first failure 2026-07-13T10:07Z, 20 minutes later.
- Still reproduces on **2.1.235** (macOS arm64, Homebrew cask).

## Root cause

The ownership check resolves symlinks on only one side of the comparison. From the 2.1.235 binary
(minified):

```js
let b = Sc.join(a, ".git", "worktrees"); // expected admin dir — plain string join, NOT symlink-resolved
v = await bc.realpath(S); // actual gitdir admin dir — realpath'd through .git → .git.nosync
if (!v || !E || yP(Sc.dirname(v)) !== yP(b) || yP(E) !== yP(Sc.join(s, ".git")))
throw new vv(`Cannot enter worktree: ${e} is not a linked worktree of ${i}.`);
```

With `.git → .git.nosync`, `dirname(v)` is always `/.git.nosync/worktrees` while `b` is always
`/.git/worktrees` — the comparison can never succeed. Verified empirically: replicating the two
expressions in a script over four affected repos yields a mismatch every time. git itself is entirely
happy with the layout; `git worktree list` shows the worktrees as registered and healthy.

## Reproduction

```bash
mkdir repo && cd repo && git init -b main && git commit --allow-empty -m init
mv .git .git.nosync && ln -s .git.nosync .git
claude # EnterWorktree(name="wt1") → works, creates .claude/worktrees/wt1
# exit; start a new session in the repo root:
# EnterWorktree(path=".claude/worktrees/wt1")
# → "Cannot enter worktree: ... is not a linked worktree of ..."
```

## Suggested fix

Resolve the expected path the same way the actual one is resolved before comparing — `realpath(b)`
with a fallback to the literal path when it doesn't exist. This compares directory identity rather
than string spelling and does not loosen the check (genuinely different directories still mismatch;
the surrounding guards are unchanged). It would also fix latent `/tmp` vs `/private/tmp` mismatches
on macOS.

## Impact

Any repo using the `.git → .git.nosync` iCloud-exclusion pattern (59 repos on this machine) loses
the ability to re-enter existing worktrees — e.g. resuming an open PR's worktree — and background
sessions must fall back to workarounds. `EnterWorktree(name=…)` creation is unaffected, which is
consistent with the creation path recording its own ownership state instead of re-deriving it.

Guide de contribution

Aucun guide de contribution indexé pour ce dépôt

Piste de recherche

Start by locating the EnterWorktree ownership check corresponding to the minified path comparisons in the report. Reproduce the case with a symlinked .git and verify the registered worktrees with git worktree list. Done means an existing worktree can be re-entered through the symlink while genuinely different directories are still rejected.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
git, javascript
Domaine
cli, tooling
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
Active
Clarté
Plutôt claire
Accessibilité débutants
55/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.