[BUG] .worktreeinclude copy drops/flattens symlinks — carried framework bundles and venvs arrive broken

Open 💬 0 comments Opened Jun 12, 2026 by optimiz-r

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

  1. 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
``

  1. Create a worktree: claude -p --worktree probe "Reply with exactly: done"
  2. Inspect the copy: ls -la .claude/worktrees/probe/fixture/
  3. Observed: only real_dir is present; both symlinks are missing. Expected: link_to_file and link_to_dir present 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 WorktreeCreate hook is not a practical workaround, since per the docs it replaces worktree creation and disables .worktreeinclude processing entirely.
  • Our current workaround is a repo SessionStart hook that detects the flattened framework and recreates the five symlinks in place — works, but only for surfaces that fire SessionStart (subagent worktrees don't).
  • Possibly related implementation-wise: #66615 (.worktreeinclude silently skips files with non-ASCII names) — both suggest the copier walks/filters entries in a way that mishandles non-regular-file cases.

View original on GitHub ↗