[BUG] .worktreeinclude copy drops/flattens symlinks — carried framework bundles and venvs arrive broken
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet (nearest distinct copier bug: #66615, non-ASCII filenames silently skipped)
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code (2.1.175, latest on npm at time of filing)
What's Wrong?
The .worktreeinclude mechanism (docs) copies gitignored files into new worktrees, but symlinks are not preserved. Observed on two surfaces, with slightly different (both broken) behavior:
- CLI (
claude -p --worktree <name>, v2.1.175): symlinks are dropped entirely — both symlinks-to-files and symlinks-to-dirs are simply absent from the worktree copy (minimal repro below). - Desktop app (parallel-session worktrees, observed 2026-06-12): symlinks-to-files are materialized as real file copies (a 13 MB binary symlink arrived as a real 13 MB file) and symlinks-to-dirs are dropped.
This breaks any carried artifact whose structure depends on symlinks. The canonical victim is a versioned macOS .framework bundle, whose entire top level is symlinks (Headers -> Versions/Current/Headers, Modules -> ..., Resources -> ..., <binary> -> Versions/Current/<binary>, Versions/Current -> A). We carry a prebuilt Rust→Apple .xcframework (~219 MB, ~10 min to rebuild — exactly what .worktreeinclude is for); in every fresh worktree the macOS slice arrives structurally broken and swift build fails with:
cannot find type 'RustBuffer' in scope
because the framework's clang module (Modules/module.modulemap, Headers/) became invisible. The same class of breakage hits Python virtualenvs (venv/bin/python* are symlinks), .app bundles, and pnpm-style node_modules layouts.
What Should Happen?
Symlinks should be preserved as symlinks, keeping their (relative) targets — the same semantics as cp -R, rsync -a, and git's own checkout. At minimum, the docs should state the limitation.
Error Messages/Logs
# Source fixture in the main checkout:
$ ls -la fixture/
drwxr-xr-x real_dir
lrwxr-xr-x link_to_dir -> real_dir
lrwxr-xr-x link_to_file -> real_dir/file.txt
# Same path in the worktree created by `claude -p --worktree probe` (CLI 2.1.175):
$ ls -la .claude/worktrees/probe/fixture/
drwxr-xr-x real_dir
# link_to_file and link_to_dir are gone entirely
Steps to Reproduce
- Create a repo with a gitignored directory containing symlinks, listed in
.worktreeinclude:
``bash``
mkdir wti-probe && cd wti-probe && git init -b main
mkdir -p fixture/real_dir && echo hi > fixture/real_dir/file.txt
ln -s real_dir/file.txt fixture/link_to_file
ln -s real_dir fixture/link_to_dir
echo "fixture/" > .gitignore
echo "fixture" > .worktreeinclude
git add .gitignore .worktreeinclude && git commit -m init
- Create a worktree:
claude -p --worktree probe "Reply with exactly: done" - Inspect the copy:
ls -la .claude/worktrees/probe/fixture/ - Observed: only
real_diris present; both symlinks are missing. Expected:link_to_fileandlink_to_dirpresent as symlinks with their original targets.
The desktop-app variant reproduces the same way via a parallel session in a repo whose .worktreeinclude lists a directory containing symlinks (e.g. any versioned macOS framework).
Claude Model
Not sure / Multiple models (model-independent — the copy happens at worktree creation)
Is this a regression?
I don't know
Claude Code Version
2.1.175 (Claude Code); also reproduced with the current desktop app on the same day
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Other (zsh; reproduced both via non-interactive claude -p --worktree and desktop-app parallel sessions)
Additional Information
- A
WorktreeCreatehook is not a practical workaround, since per the docs it replaces worktree creation and disables.worktreeincludeprocessing entirely. - Our current workaround is a repo
SessionStarthook that detects the flattened framework and recreates the five symlinks in place — works, but only for surfaces that fireSessionStart(subagent worktrees don't). - Possibly related implementation-wise: #66615 (
.worktreeincludesilently skips files with non-ASCII names) — both suggest the copier walks/filters entries in a way that mishandles non-regular-file cases.