Skip to content

perf(glob): track active component indices as a set to eliminate double-visits - #28496

Merged
Jarred-Sumner merged 3 commits into
mainfrom
claude/glob-doublestar-redundant-push
Mar 25, 2026
Merged

Jarred-Sumner merged 3 commits into
mainfrom
claude/glob-doublestar-redundant-push

Conversation

@dylan-conway

@dylan-conway dylan-conway commented Mar 24, 2026 •

Copy link
Copy Markdown
Member

What

Eliminates redundant directory visits in Bun.Glob.scan() for patterns
containing **/X/.... Directories under the boundary were being opened
and read twice; they're now read once.

Why

A glob pattern like **/node_modules/**/*.js is split into components:

idx: 0    1             2    3
     **   node_modules  **   *.js

When the walker is at ** (idx 0) and encounters a directory named
node_modules, there are two valid interpretations:

  • Advance: this is the node_modules we were looking for — jump to
    idx 2 and match *.js inside.
  • Keep going: ** matches zero or more dirs, so node_modules might
    just be another directory under **. Stay at idx 0 in case there's
    another node_modules deeper.

Both are correct and both can produce matches. The previous
implementation handled this by pushing two WorkItems for the same
path — one at idx 0, one at idx 2. Each triggered its own
openat + readdir + close, and since both recurse, every descendant of
the boundary was read twice.

How

Instead of forking the traversal, carry a set of active component
indices per WorkItem. When a boundary is crossed, both indices go into
one set; the directory is iterated once, and each entry is checked
against all active positions. The child's set is the union of what each
active index says to do next.

Additionally, when the component after a **/X boundary is itself **
(as in **/node_modules/**/*.js), the outer ** is dropped entirely —
the inner one's recursion already covers everything the outer one would
find.

This is the standard NFA state-set simulation, applied to filesystem
traversal.

Benchmarks

**/node_modules/**/*.js with { dot: true } on the next.js repo
(68,233 matches, macOS arm64):

Screenshot 2026-03-23 at 10 36 43 PM

Gain scales with how much of the tree is under the **/X boundary.
Synthetic trees with deeper nesting hit 2.9×. Patterns without a
boundary (**/*.ts) are unchanged.

Implementation

  • ComponentSet is an alias for bun.bit_set.AutoBitSet, which stores
    up to 127 indices inline in [2]usize and spills to heap beyond —
    patterns of any depth stay correct.
  • Added count(), findFirstSet(), and iterator() dispatch methods
    to AutoBitSet.
  • evalDir/evalFile/evalImpl replace the inline per-index logic;
    they iterate the active set and accumulate results.
  • Net +35 lines (the set helpers collapsed triplicated bump-handling
    blocks in the old code).
  • Added a test with 130-component patterns to cover the heap-backed
    path.

At a `**/X` boundary the walker has two valid interpretations: advance
past X, or keep the outer `**` alive (in case X appears again deeper).
The previous implementation pushed two WorkItems for the same directory,
each triggering a separate openat + readdir + close. The overhead
cascaded to every descendant.

Instead, carry a set of active component indices per WorkItem (an NFA
state set) and evaluate each directory entry against all active indices
in one readdir. The child's set is the union of recursion targets. When
the component after a `**/X` boundary is itself `**`, the outer
globstar is dropped entirely since the inner one's recursion subsumes it.

ComponentSet is an alias for bun.bit_set.AutoBitSet, which stores up to
127 indices inline in a [2]usize and spills to heap beyond, so patterns
of any depth remain correct. Added count/findFirstSet/iterator dispatch
methods to AutoBitSet.

Benchmark on next.js (`**/node_modules/**/*.js`, dot:true, 68K matches):

    before: 2.962 s
    after:  1.305 s  (2.27x)

Synthetic trees with deeper nesting show up to 2.9x. Patterns without a
`**/X` boundary (e.g. `**/*.ts`) are unchanged.
@robobun

robobun commented Mar 24, 2026 •

Copy link
Copy Markdown
Collaborator
Updated 5:11 PM PT - Mar 24th, 2026

❌ @dylan-conway, your commit 0c903b3 has 4 failures in Build #41691 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 28496

That installs a local version of the PR into your bun-28496 executable, so you can run:

bun-28496 --bun

@coderabbitai

coderabbitai Bot commented Mar 24, 2026 •

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: df6a7a0e-949e-4fd7-b341-bb65a0ede6a0

📥 Commits

Reviewing files that changed from the base of the PR and between c8412c7 and 286144c.

📒 Files selected for processing (2)
  • src/glob/GlobWalker.zig
  • test/js/bun/glob/scan.test.ts

Walkthrough

Reworked glob component tracking to use a bitset-based ComponentSet for multiple active pattern indices. Added iterator and query APIs to AutoBitSet. Updated GlobWalker traversal and matching to operate over ComponentSet masks. Added a test exercising deep patterns (130 components) across several glob variants.

Changes

Cohort / File(s) Summary
AutoBitSet API Enhancements
src/collections/bit_set.zig
Added pub fn count, pub fn findFirstSet, and pub fn iterator on AutoBitSet, plus pub fn Iterator(comptime options: IteratorOptions) type — a tagged-union iterator that forwards next() to static/dynamic variants.
GlobWalker Pattern Component Refactoring
src/glob/GlobWalker.zig
Replaced single component_idx state with ComponentSet bitset in IterState.Directory and WorkItem. Added makeSet/singleSet, evalDir/evalFile/evalImpl, and normalizeIdx. Adjusted traversal, matching, literal-tail optimization, and symlink/unknown handling to use multi-index masks.
Deep-pattern Test
test/js/bun/glob/scan.test.ts
Added patterns with many components test that constructs a 130-component directory tree and asserts correct matches for three glob pattern variants; skipped on Windows.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main performance optimization: tracking active component indices as a set to eliminate redundant directory visits in glob pattern matching.
Description check ✅ Passed The description provides comprehensive context following the template: clear 'What' and 'How' sections explaining the problem, solution approach, benchmarks, and implementation details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/glob/GlobWalker.zig`:
- Around line 1404-1411: The call to collapseSuccessiveDoubleWildcards uses
`@constCast` because collapseSuccessiveDoubleWildcards currently takes a mutable
GlobWalker pointer; change collapseSuccessiveDoubleWildcards to accept *const
GlobWalker so it can be called from normalizeIdx without casting, update its
function signature and any other callers to pass *const where appropriate, and
remove the `@constCast` in normalizeIdx (reference functions: normalizeIdx and
collapseSuccessiveDoubleWildcards).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9b92c39e-cfba-4db5-82ed-7484413b811e

📥 Commits

Reviewing files that changed from the base of the PR and between 639bc43 and c8412c7.

📒 Files selected for processing (3)
  • src/collections/bit_set.zig
  • src/glob/GlobWalker.zig
  • test/js/bun/glob/scan.test.ts

Comment thread src/glob/GlobWalker.zig
Comment on lines +1404 to +1411
inline fn normalizeIdx(this: *const GlobWalker, idx: u32) u32 {
if (idx < this.patternComponents.items.len and
this.patternComponents.items[idx].syntax_hint == .Double)
{
return @constCast(this).collapseSuccessiveDoubleWildcards(idx);
}
return idx;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider avoiding @constCast by changing function signature.

collapseSuccessiveDoubleWildcards only reads state, so it could take *const GlobWalker. This would eliminate the need for @constCast here.

♻️ Optional refactor
-    fn collapseSuccessiveDoubleWildcards(this: *GlobWalker, idx: u32) u32 {
+    fn collapseSuccessiveDoubleWildcards(this: *const GlobWalker, idx: u32) u32 {

Then in normalizeIdx:

-            return `@constCast`(this).collapseSuccessiveDoubleWildcards(idx);
+            return this.collapseSuccessiveDoubleWildcards(idx);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/glob/GlobWalker.zig` around lines 1404 - 1411, The call to
collapseSuccessiveDoubleWildcards uses `@constCast` because
collapseSuccessiveDoubleWildcards currently takes a mutable GlobWalker pointer;
change collapseSuccessiveDoubleWildcards to accept *const GlobWalker so it can
be called from normalizeIdx without casting, update its function signature and
any other callers to pass *const where appropriate, and remove the `@constCast` in
normalizeIdx (reference functions: normalizeIdx and
collapseSuccessiveDoubleWildcards).

@github-actions

Copy link
Copy Markdown
Contributor

Found 6 issues this PR may fix:

  1. Bun.Glob Cannot scan completely #21300 - Eliminates redundant directory visits that could cause incomplete scanning on Windows
  2. Glob scan errors out if any file cannot be read #19631 - Reduces filesystem operations by 50%, decreasing chances of permission errors terminating scans
  3. Glob().match() fails when ** is used inside braces #25392 - Improved NFA state-set simulation could fix pattern matching with ** in braces
  4. fs.glob never matches dot files, even with explicit dot patterns #28021 - Optimized directory traversal may resolve dot file matching edge cases
  5. Bun's Glob.scan and fs.glob do not detect unix domain sockets #17506 - Refactored file type detection logic could handle special file types like unix domain sockets
  6. bun run fails with CouldntReadCurrentDirectory when ancestor directories are not readable #28220 - Fewer openat calls reduce permission failures in sandboxed environments

If this is helpful, consider adding Fixes # to the PR description to auto-close the issue on merge.

🤖 Generated with Claude Code

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No bugs found, but this is a significant NFA-simulation refactor of the core glob walker traversal — the algorithmic change is large enough to warrant a human look before merging.

Extended reasoning...

Overview

This PR touches three files: src/collections/bit_set.zig (adds count(), findFirstSet(), and iterator() dispatch methods to AutoBitSet), src/glob/GlobWalker.zig (the core change), and test/js/bun/glob/scan.test.ts (new deep-component regression test). The GlobWalker change replaces the per-WorkItem scalar idx: u32 with a ComponentSet (AutoBitSet bitmask of active NFA states), eliminating duplicate openat + readdir + close calls at **/X boundaries.

Security risks

None apparent. This is a pure performance/correctness refactor of filesystem traversal; it does not handle credentials, permissions, or untrusted input in new ways.

Level of scrutiny

High. The change implements a non-trivial NFA state-set simulation over the filesystem. Core logic in evalDir / evalFile / evalImpl replaces three previously-duplicated inline blocks, and the "drop outer ** when inner component is also **" optimization is subtle enough to warrant careful review of all **/X/** edge cases. The set is created on every directory entry visit (makeSet() allocates from the arena each time), which is fine for ≤127-component patterns (static path, no heap) but adds arena pressure for deep patterns. The test covers the heap-backed path but does not exhaustively exercise all boundary-pattern combinations.

Other factors

The PR description is thorough and the benchmarks are compelling (2.27× for **/node_modules/**/*.js). The implementation follows established NFA simulation patterns correctly as far as can be determined from static analysis. However, at ~250 changed lines in production-critical filesystem traversal code, this merits a human sign-off before landing.

- Use bun.handleOom instead of catch bun.outOfMemory() in makeSet
- Skip the 130-component test on Windows where the path exceeds
  MAX_PATH (260). The AutoBitSet heap path is still covered on
  other platforms.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No bugs found, but this is a substantial algorithmic refactoring of the core glob walker that implements NFA state-set simulation — it warrants a human look before merging.

Extended reasoning...

Overview

This PR replaces the single component_idx integer in WorkItem with a ComponentSet (an AutoBitSet) to track multiple active NFA states simultaneously during directory traversal. Changes span src/collections/bit_set.zig (new count, findFirstSet, iterator methods on AutoBitSet), src/glob/GlobWalker.zig (major structural refactor with new evalDir/evalFile/evalImpl/makeSet/singleSet/normalizeIdx helpers), and a new 130-component test.

Security risks

None. This touches only glob pattern matching and filesystem traversal logic — no auth, crypto, or permission-sensitive code paths.

Level of scrutiny

High. The glob walker is production-critical (used by Bun.Glob.scan()) and the change is a non-trivial algorithmic refactoring. The NFA state-set simulation has subtle correctness requirements: e.g., the bump == 2 path in evalDir relies on the invariant that idx + 2 < len (which holds because matchPatternDir only returns 2 when component_idx + 1 != len - 1), and the decision to drop the outer ** when idx+2 is itself ** needs careful validation. These are correct per static analysis, but given the complexity, a human should verify the full NFA simulation logic.

Other factors

The PR is well-described with benchmarks (2.27× speedup on real-world data), the existing fast-glob e2e test suite provides broad coverage of pattern semantics, and the new 130-component test covers the heap-backed AutoBitSet path. The only reviewer comment is a trivial @constCast style nitpick from CodeRabbit. The change is net +35 lines and cleanly structured.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No bugs found, but this is a substantial algorithmic refactor of the glob walker’s core traversal loop that warrants a human look before merging.

Extended reasoning...

Overview

This PR changes GlobWalker.zig to track multiple active pattern-component indices per WorkItem using an AutoBitSet (ComponentSet), replacing the previous single component_idx scalar. It also adds count(), findFirstSet(), and iterator() to AutoBitSet in bit_set.zig, and adds a 130-component depth test to scan.test.ts.

Security risks

None. This is a pure performance optimization with no auth, crypto, or permission-handling changes.

Level of scrutiny

High. The change rewrites the inner loop of Bun.Glob.scan() — the .file, .directory, .sym_link, and .unknown branches all switch from inline single-index logic to evalFile/evalDir/evalImpl helpers that iterate a bitset. The evalDir boundary condition (if (bump == 2 and comps[idx + 2].syntax_hint != .Double)) is a subtle invariant that depends on matchPatternDir never returning 2 when idx + 2 >= len — true by inspection but easy to regress. The normalizeIdx helper and its @constCast workaround add further complexity.

Other factors

The algorithm (NFA state-set simulation) is correct in principle and the benchmarks are convincing. The test covers the heap-backed AutoBitSet path. No bugs were flagged by the automated hunting system. The only reviewer comment is a trivial @constCast nitpick. Given the breadth of the traversal code touched, a human reviewer familiar with the glob walker should sign off.

@Jarred-Sumner
Jarred-Sumner merged commit e59a147 into main Mar 25, 2026
57 of 64 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the claude/glob-doublestar-redundant-push branch March 25, 2026 00:26
sosukesuzuki added a commit that referenced this pull request Mar 25, 2026
PR #28496 replaced the single component_idx with an active BitSet, but
the Windows NtQueryDirectoryFile filter call from PR #28489 still
referenced the old variable name.

computeNtFilter operates on a single pattern component, so apply the
kernel filter only when exactly one index is active. For multi-index
states (e.g. after **), skip the filter to avoid hiding entries needed
by other indices. The filter is a pre-filter optimization;
matchPatternImpl still runs for correctness.
sosukesuzuki added a commit that referenced this pull request Mar 25, 2026
PR #28496 replaced the single component_idx with an active BitSet, but
the Windows NtQueryDirectoryFile filter call from PR #28489 still
referenced the old variable name.

computeNtFilter operates on a single pattern component, so apply the
kernel filter only when exactly one index is active. For multi-index
states (e.g. after **), skip the filter to avoid hiding entries needed
by other indices. The filter is a pre-filter optimization;
matchPatternImpl still runs for correctness.
Jarred-Sumner pushed a commit that referenced this pull request Mar 25, 2026
## What

Fixes a build error on `main` introduced by the interaction of #28496
and #28489:

```
src/glob/GlobWalker.zig:715:69: error: use of undeclared identifier 'component_idx'
                        iterator.setNameFilter(this.computeNtFilter(component_idx));
                                                                    ^~~~~~~~~~~~~
```

## Why

\#28496 replaced the single `component_idx` variable with an `active`
BitSet to track multiple active pattern component indices. \#28489
(merged just before) added a Windows `NtQueryDirectoryFile` filter call
that references `component_idx`. After both landed, the variable no
longer exists in scope.

## Fix

`computeNtFilter` operates on a single pattern component, so it can only
be applied when exactly one index is active. For multi-index states
(e.g. after `**`), a single-component kernel filter could hide entries
that other active indices need to match, so we skip it.

This is safe because the NT filter is purely a pre-filter optimization —
`matchPatternImpl` still runs on every returned entry for correctness
(per the existing doc comment on `computeNtFilter`).

## Verification

`bun run zig:check-all` passes on all platforms (macOS/Linux/Windows ×
x86_64/aarch64 × Debug/Release).
structwafel pushed a commit to structwafel/bun that referenced this pull request Apr 25, 2026
…le-visits (oven-sh#28496)

## What

Eliminates redundant directory visits in `Bun.Glob.scan()` for patterns
containing `**/X/...`. Directories under the boundary were being opened
and read twice; they're now read once.

## Why

A glob pattern like `**/node_modules/**/*.js` is split into components:

    idx: 0    1             2    3
         **   node_modules  **   *.js

When the walker is at `**` (idx 0) and encounters a directory named
`node_modules`, there are two valid interpretations:

- **Advance**: this is the `node_modules` we were looking for — jump to
  idx 2 and match `*.js` inside.
- **Keep going**: `**` matches zero or more dirs, so `node_modules`
might
  just be another directory under `**`. Stay at idx 0 in case there's
  another `node_modules` deeper.

Both are correct and both can produce matches. The previous
implementation handled this by pushing two `WorkItem`s for the same
path — one at idx 0, one at idx 2. Each triggered its own
`openat + readdir + close`, and since both recurse, every descendant of
the boundary was read twice.

## How

Instead of forking the traversal, carry a **set** of active component
indices per `WorkItem`. When a boundary is crossed, both indices go into
one set; the directory is iterated once, and each entry is checked
against all active positions. The child's set is the union of what each
active index says to do next.

Additionally, when the component after a `**/X` boundary is itself `**`
(as in `**/node_modules/**/*.js`), the outer `**` is dropped entirely —
the inner one's recursion already covers everything the outer one would
find.

This is the standard NFA state-set simulation, applied to filesystem
traversal.

## Benchmarks

`**/node_modules/**/*.js` with `{ dot: true }` on the next.js repo
(68,233 matches, macOS arm64):

<img width="1664" height="537" alt="Screenshot 2026-03-23 at 10 36
43 PM"
src="https://github.com/user-attachments/assets/57ffb5ba-0f4f-4536-9339-fc38f669d4cf"
/>

Gain scales with how much of the tree is under the `**/X` boundary.
Synthetic trees with deeper nesting hit 2.9×. Patterns without a
boundary (`**/*.ts`) are unchanged.

## Implementation

- `ComponentSet` is an alias for `bun.bit_set.AutoBitSet`, which stores
  up to 127 indices inline in `[2]usize` and spills to heap beyond —
  patterns of any depth stay correct.
- Added `count()`, `findFirstSet()`, and `iterator()` dispatch methods
  to `AutoBitSet`.
- `evalDir`/`evalFile`/`evalImpl` replace the inline per-index logic;
  they iterate the active set and accumulate results.
- Net +35 lines (the set helpers collapsed triplicated bump-handling
  blocks in the old code).
- Added a test with 130-component patterns to cover the heap-backed
  path.
structwafel pushed a commit to structwafel/bun that referenced this pull request Apr 25, 2026
…#28543)

## What

Fixes a build error on `main` introduced by the interaction of oven-sh#28496
and oven-sh#28489:

```
src/glob/GlobWalker.zig:715:69: error: use of undeclared identifier 'component_idx'
                        iterator.setNameFilter(this.computeNtFilter(component_idx));
                                                                    ^~~~~~~~~~~~~
```

## Why

\oven-sh#28496 replaced the single `component_idx` variable with an `active`
BitSet to track multiple active pattern component indices. \oven-sh#28489
(merged just before) added a Windows `NtQueryDirectoryFile` filter call
that references `component_idx`. After both landed, the variable no
longer exists in scope.

## Fix

`computeNtFilter` operates on a single pattern component, so it can only
be applied when exactly one index is active. For multi-index states
(e.g. after `**`), a single-component kernel filter could hide entries
that other active indices need to match, so we skip it.

This is safe because the NT filter is purely a pre-filter optimization —
`matchPatternImpl` still runs on every returned entry for correctness
(per the existing doc comment on `computeNtFilter`).

## Verification

`bun run zig:check-all` passes on all platforms (macOS/Linux/Windows ×
x86_64/aarch64 × Debug/Release).
xhjkl pushed a commit to xhjkl/bun that referenced this pull request May 14, 2026
…le-visits (oven-sh#28496)

## What

Eliminates redundant directory visits in `Bun.Glob.scan()` for patterns
containing `**/X/...`. Directories under the boundary were being opened
and read twice; they're now read once.

## Why

A glob pattern like `**/node_modules/**/*.js` is split into components:

    idx: 0    1             2    3
         **   node_modules  **   *.js

When the walker is at `**` (idx 0) and encounters a directory named
`node_modules`, there are two valid interpretations:

- **Advance**: this is the `node_modules` we were looking for — jump to
  idx 2 and match `*.js` inside.
- **Keep going**: `**` matches zero or more dirs, so `node_modules`
might
  just be another directory under `**`. Stay at idx 0 in case there's
  another `node_modules` deeper.

Both are correct and both can produce matches. The previous
implementation handled this by pushing two `WorkItem`s for the same
path — one at idx 0, one at idx 2. Each triggered its own
`openat + readdir + close`, and since both recurse, every descendant of
the boundary was read twice.

## How

Instead of forking the traversal, carry a **set** of active component
indices per `WorkItem`. When a boundary is crossed, both indices go into
one set; the directory is iterated once, and each entry is checked
against all active positions. The child's set is the union of what each
active index says to do next.

Additionally, when the component after a `**/X` boundary is itself `**`
(as in `**/node_modules/**/*.js`), the outer `**` is dropped entirely —
the inner one's recursion already covers everything the outer one would
find.

This is the standard NFA state-set simulation, applied to filesystem
traversal.

## Benchmarks

`**/node_modules/**/*.js` with `{ dot: true }` on the next.js repo
(68,233 matches, macOS arm64):

<img width="1664" height="537" alt="Screenshot 2026-03-23 at 10 36
43 PM"
src="https://github.com/user-attachments/assets/57ffb5ba-0f4f-4536-9339-fc38f669d4cf"
/>

Gain scales with how much of the tree is under the `**/X` boundary.
Synthetic trees with deeper nesting hit 2.9×. Patterns without a
boundary (`**/*.ts`) are unchanged.

## Implementation

- `ComponentSet` is an alias for `bun.bit_set.AutoBitSet`, which stores
  up to 127 indices inline in `[2]usize` and spills to heap beyond —
  patterns of any depth stay correct.
- Added `count()`, `findFirstSet()`, and `iterator()` dispatch methods
  to `AutoBitSet`.
- `evalDir`/`evalFile`/`evalImpl` replace the inline per-index logic;
  they iterate the active set and accumulate results.
- Net +35 lines (the set helpers collapsed triplicated bump-handling
  blocks in the old code).
- Added a test with 130-component patterns to cover the heap-backed
  path.
xhjkl pushed a commit to xhjkl/bun that referenced this pull request May 14, 2026
…#28543)

## What

Fixes a build error on `main` introduced by the interaction of oven-sh#28496
and oven-sh#28489:

```
src/glob/GlobWalker.zig:715:69: error: use of undeclared identifier 'component_idx'
                        iterator.setNameFilter(this.computeNtFilter(component_idx));
                                                                    ^~~~~~~~~~~~~
```

## Why

\oven-sh#28496 replaced the single `component_idx` variable with an `active`
BitSet to track multiple active pattern component indices. \oven-sh#28489
(merged just before) added a Windows `NtQueryDirectoryFile` filter call
that references `component_idx`. After both landed, the variable no
longer exists in scope.

## Fix

`computeNtFilter` operates on a single pattern component, so it can only
be applied when exactly one index is active. For multi-index states
(e.g. after `**`), a single-component kernel filter could hide entries
that other active indices need to match, so we skip it.

This is safe because the NT filter is purely a pre-filter optimization —
`matchPatternImpl` still runs on every returned entry for correctness
(per the existing doc comment on `computeNtFilter`).

## Verification

`bun run zig:check-all` passes on all platforms (macOS/Linux/Windows ×
x86_64/aarch64 × Debug/Release).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants