fix: anchor .claude ignore patterns to the project root - #206
Merged
Conversation
Both patterns matched any ancestor path segment, so checking the repo out under a `.claude/` path (e.g. a git worktree in `.claude/worktrees/`) silently disabled the quality gates: - vitest matches `coverage.exclude` against absolute paths with picomatch's `contains` option, so `.claude/**` excluded every source file. Coverage reported `0/0` and the 95%/90% thresholds passed vacuously while measuring nothing. Anchor it with the config's own absolute directory. - biome resolved `!**/.claude` against an ancestor directory, so `biome check .` reported "Checked 0 files". `!.claude` is anchored to the project root. `test.exclude` is globbed by tinyglobby relative to the project root, so it is already anchored and stays relative.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Coverage Report
File CoverageNo changed files found. |
7nohe
added a commit
that referenced
this pull request
Aug 11, 2026
`coverage.exclude` patterns are matched against absolute paths with picomatch's `contains` option, so every relative spelling of a `.claude` ignore also matches the project's own root when the repo is checked out under a `.claude/` path, as agent git worktrees in `.claude/worktrees/` are. That excluded every source file and let the thresholds pass against 0/0. All four relative forms were measured to fail open: .claude/** ./.claude/** /.claude/** **/.claude/** #206 fixed this by building an absolute pattern from `import.meta.url`. `coverage.include` is anchored to the project root instead, so scoping by include removes the failure mode structurally rather than working around it, and drops the `node:url` import, the derived root and five exclude entries along with it. `test.exclude` keeps its `.claude/**` entry: it is globbed relative to the project root and is load-bearing at the repo root, where dropping it collects the nested worktrees too (75 test files instead of 15). Coverage output is unchanged in both locations: 187/187 tests, 529/537 statements, no `.claude` paths in the report.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Both ignore patterns matched any ancestor path segment named
.claude, not just a.claudedirectory inside the project. When the repo is checked out under a.claude/path — which is how Claude Code worktree sessions run (.claude/worktrees/<name>) — this silently disabled both quality gates.Coverage (the dangerous one). Vitest matches
coverage.excludeagainst absolute paths with picomatch'scontainsoption:so
.claude/**matched every source file.npm testreported:The configured thresholds (95% lines/functions/statements, 90% branches) then passed vacuously — a green
npm testmeasuring nothing.Lint. Biome resolved
!**/.claudeagainst an ancestor directory, sobiome check .reportedChecked 0 files/ "No files were processed in the specified paths" instead of linting.Fix
vitest.config.ts: build the exclude from the config file's own absolute directory (${projectRoot}.claude/**). Since the pattern is matched against absolute paths incontainsmode, an absolute prefix is the only available anchor. Backslashes are normalized because vitest slashes the file path but not the pattern, and Windows is in the CI matrix.biome.json:!**/.claude→!.claude, which is anchored to the project root.!/.claudeand!./.claudewere tested and do not work — Biome descends into.claude/worktrees/with either.test.excludedeliberately keeps.claude/**: it is globbed by tinyglobby relative to the project root, so it is already anchored and never broke. It is also the load-bearing pattern that stops a main-checkout run from collecting.claude/worktrees/*/tests/*.test.ts, and an absolute pattern is not reliably supported by tinyglobby'signore. That picomatch/tinyglobby asymmetry is why onlycoverage.excludechanges.Verification
Main checkout (
/…/openapi-react-query-codegen), before → after:biome check ..claude)Throwaway worktree under
.claude/worktrees/, before → after:biome check .No files were processed)0/0,Unknown%Both patterns also narrow from any-depth to root-only, so a hypothetical nested
.claude(e.g.examples/foo/.claude) would no longer be ignored. The identical main-checkout file count (109) and identical coverage numbers show no such directory exists today.The repo's own
.claude/directory is still ignored:src/*.mtspresent under.claude/worktrees/; had the anchor stopped suppressing descent, that number would explode. The biome report contains 0.claudepaths.own .claude (want false): false), not by a run. With nocoverage.includeset,getUntestedFilesearly-returns, so coverage only ever sees files the tests actually load and nothing under.claude/is ever loaded; the pattern is belt-and-braces there. (Thevitest list --filesOnlyresult above exercisestest.exclude, which is unchanged.)The picomatch call vitest actually makes was also asserted directly for posix and Windows-style roots: