[BUG] .worktreeinclude silently copies only part of a matched gitignored directory (110/564 files, ~9.8MB cutoff); file-globs under an ignored parent match nothing

Status Open
Maintainer reply None cached
Activity 0 comments · opened Aug 1, 2026

Bug Description

.worktreeinclude silently copies only part of a matched gitignored directory. In my case a directory with 564 gitignored files / 24 MB produced only 110 files / 9.8 MB in the new worktree — no warning, no error, exit 0. The worktree looks provisioned but is missing 80% of the files the pattern matched.

The docs state the contract without any qualifier about size or count:

The file uses .gitignore syntax. Only files that match a pattern and are also gitignored are copied, so tracked files are never duplicated.

Two distinct problems, both silent:

  1. Directory-form pattern truncates. data/fixtures/ matched, but copying stopped at ~9.8 MB (110 of 564 files). The cutoff is not alphabetical — I verified the copied set is not a lexicographic prefix of the full set.
  2. File-glob pattern inside a gitignored directory matches nothing. data/fixtures/aps_*.txt copied 0 files, even though git check-ignore -v reports every one of those files as ignored. Only the directory form matches at all. This looks related to #79424 (leading **/ silently matching nothing) — same class: a valid gitignore-syntax pattern that the copier silently does not honor.

The combination is worse than either alone: the only pattern that copies anything is the one that truncates, so you cannot express "copy just these 334 files" and you cannot get the full directory either.

Why partial is worse than none: each worktree receives a different subset, so test runs are not reproducible across worktrees. A hard failure would have been safe; silent truncation reads as success.

Steps to Reproduce

mkdir repro && cd repro && git init
printf 'data/*\n.claude/worktrees/\n' > .gitignore
mkdir -p data/fixtures
# 600 files x ~50KB = ~30MB, all gitignored
for i in $(seq 1 600); do head -c 50000 /dev/urandom | base64 > "data/fixtures/f_$i.txt"; done
git add -A && git commit -m init

# sanity: they are ignored
git check-ignore -v data/fixtures/f_1.txt      # .gitignore:1:data/*  data/fixtures/f_1.txt

# case 1: directory form -> partial copy, no warning
printf 'data/fixtures/\n' > .worktreeinclude
claude -w t1 -p "ok"
ls .claude/worktrees/t1/data/fixtures | wc -l   # EXPECTED 600, ACTUAL: truncated
du -sh .claude/worktrees/t1/data/fixtures       # stops around ~10MB

# case 2: file glob -> copies nothing, no warning
printf 'data/fixtures/f_*.txt\n' > .worktreeinclude
claude -w t2 -p "ok"
ls .claude/worktrees/t2/data/fixtures 2>/dev/null | wc -l   # ACTUAL: 0

Expected Behavior

Either copy everything the pattern matches, or fail loudly / warn on stdout naming what was skipped and why. Silent truncation makes a half-provisioned worktree indistinguishable from a correct one.

For case 2, file-level globs under a gitignored directory should match, matching the documented .gitignore syntax contract and git check-ignore behavior.

Actual Behavior

  • Directory form: 110 of 564 files (9.8 MB of 24 MB) copied, exit 0, no output.
  • File glob: 0 of 334 matching files copied, exit 0, no output.

Environment

  • Claude Code v2.1.220
  • Linux 5.15 (Ubuntu), /bin/sh -> dash
  • Repo where .gitignore excludes a parent directory (data/*), so all files inside are ignored via the parent

Notes

I could not find an existing issue for the truncation specifically. #79424 covers a related silent pattern-match failure, and #70466 covers EnterWorktree not processing .worktreeinclude — I tested the latter on v2.1.220 with a listed vs. unlisted probe file and it does not reproduce (the listed file is copied, the unlisted one is not), so EnterWorktree honors .worktreeinclude in this version.

Workaround for anyone hitting this: if the files are test fixtures, commit them instead of carrying them via .worktreeinclude — that is what I ended up doing (334 files / 2.3 MB), and it also fixes clean-checkout and CI, which had the same failure for the same reason.

View original on GitHub ↗