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)
- 主要语言
- Python
- 星标
- 145k
- 派生
- 23.1k
- PR 合并指标
- PR 指标待抓取
描述
## 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.
贡献指南
这个仓库没有索引到贡献指南
调研方向
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.
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- git, javascript
- 领域
- cli, tooling
- Issue 类型
- 缺陷
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 活跃度
- 活跃
- 描述清晰度
- 基本清楚
- 新手友好度
- 55/100