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)

Aperta
#89,181 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
area:core bug has repro platform:macos regression
Lingua principale
Python
Stelle
145k
Fork
23.1k
Metriche di merge delle PR
Metriche PR in attesa

Descrizione

## 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.

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Direzione di ricerca

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.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
git, javascript
Ambito
cli, tooling
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Attiva
Chiarezza
Abbastanza chiara
Idoneità per principianti
55/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.