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)

未关闭
#89,181 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
area:core bug has repro platform:macos regression
主要语言
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

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。