Describe the Bug
Running pyrefly check inside a linked git worktree placed under a hidden directory of the main checkout — the layout several agent tools create (.claude/worktrees/<name>/, and ~/.codex/worktrees/… as in #2402) — skips every include with No Python files matched. Two independent mechanisms cause it:
1. The ignore-file search does not honour repository boundaries.
A linked worktree's .git is a gitdir: … pointer file, so the upward search for .gitignore/.ignore/.git/info/exclude walks straight past the working-tree root and finds the enclosing checkout's .git/info/exclude, applying its patterns rooted at the enclosing checkout. A pattern like **/.claude/worktrees/ in the main checkout's exclude file (how these tools keep worktrees out of git status) then matches every file inside the worktree:
ignore files [<worktree>/.gitignore, /path/to/main-checkout/.git/info/exclude]
No Python files matched patterns …
git's own semantics: a directory carrying a .git entry (file or directory) is a working-tree root; an outer repository's ignore rules never apply inside it, and the repository's shared info/exclude applies to each worktree relative to that worktree's own root (git -C <worktree> check-ignore confirms the files are not ignored).
2. HiddenDirFilter::RelativeTo only carries import_root (follow-up to #2402).
For a src-layout project import_root is <project>/src, so an include outside it — tests/check.py — matches no root and falls back to checking the absolute path's components, where a checkout under .claude/… has every chain hidden; the include is skipped even though the project's own files contain no hidden component. Additionally, the root list is only populated when use_ignore_files is true, so --use-ignore-files=false silently degrades the filter to All — disabling one exclusion mechanism makes another stricter.
Reproduction
git init main && cd main && python -c 'open("pyproject.toml","w")'
printf '**/wt/\n' >> .git/info/exclude
git worktree add .claude/worktrees/wt
cd .claude/worktrees/wt
printf '[tool.pyrefly]\nproject-includes=["src"]\n' > pyproject.toml
mkdir src && touch src/m.py
pyrefly check # every include skipped
Expected behavior
The worktree checks like any other checkout: outer ignore rules stop at the working-tree boundary, the shared info/exclude applies relative to the worktree root, and hidden ancestors above the project's include roots don't hide the project's own files.
I have a fix for both halves (boundary-aware ignore-file search that resolves gitdir/commondir for linked worktrees, and RelativeTo built from every include root plus the import root, decoupled from use_ignore_files) — PR incoming, happy to adjust the approach.
Describe the Bug
Running
pyrefly checkinside a linked git worktree placed under a hidden directory of the main checkout — the layout several agent tools create (.claude/worktrees/<name>/, and~/.codex/worktrees/…as in #2402) — skips every include withNo Python files matched. Two independent mechanisms cause it:1. The ignore-file search does not honour repository boundaries.
A linked worktree's
.gitis agitdir: …pointer file, so the upward search for.gitignore/.ignore/.git/info/excludewalks straight past the working-tree root and finds the enclosing checkout's.git/info/exclude, applying its patterns rooted at the enclosing checkout. A pattern like**/.claude/worktrees/in the main checkout's exclude file (how these tools keep worktrees out ofgit status) then matches every file inside the worktree:git's own semantics: a directory carrying a
.gitentry (file or directory) is a working-tree root; an outer repository's ignore rules never apply inside it, and the repository's sharedinfo/excludeapplies to each worktree relative to that worktree's own root (git -C <worktree> check-ignoreconfirms the files are not ignored).2.
HiddenDirFilter::RelativeToonly carriesimport_root(follow-up to #2402).For a src-layout project
import_rootis<project>/src, so an include outside it —tests/check.py— matches no root and falls back to checking the absolute path's components, where a checkout under.claude/…has every chain hidden; the include is skipped even though the project's own files contain no hidden component. Additionally, the root list is only populated whenuse_ignore_filesis true, so--use-ignore-files=falsesilently degrades the filter toAll— disabling one exclusion mechanism makes another stricter.Reproduction
Expected behavior
The worktree checks like any other checkout: outer ignore rules stop at the working-tree boundary, the shared
info/excludeapplies relative to the worktree root, and hidden ancestors above the project's include roots don't hide the project's own files.I have a fix for both halves (boundary-aware ignore-file search that resolves
gitdir/commondirfor linked worktrees, andRelativeTobuilt from every include root plus the import root, decoupled fromuse_ignore_files) — PR incoming, happy to adjust the approach.