Skip to content

Conversation

@mmitchellg5
Copy link

@mmitchellg5 mmitchellg5 commented Jan 5, 2026

What does this PR do?

Fixes #24007
Possibly fixes #18902, #7412

Some filesystems (bind mounts, FUSE, NFS) don't provide d_type in directory entries, returning DT_UNKNOWN. This caused glob and recursive readdir to skip entries entirely.

Problem

On Linux filesystems that don't populate d_type in directory entries (bind mounts, FUSE, NFS, some ext4 configurations), readdir() returns DT_UNKNOWN instead of the actual file type. This caused:

  • Bun.Glob to skip files/directories entirely
  • fs.readdirSync(..., {recursive: true}) to not recurse into subdirectories
  • fs.readdirSync(..., {withFileTypes: true}) to report incorrect types

Solution

Implemented a lazy lstatat() fallback when d_type == DT_UNKNOWN:

  • sys.zig: Added lstatat() function - same as fstatat() but with AT_SYMLINK_NOFOLLOW flag to correctly identify symlinks
  • GlobWalker.zig: When encountering .unknown entries, first check if filename matches pattern, then call lstatat() only if needed
  • node_fs.zig: Handle .unknown in both async and sync recursive readdir paths; propagate resolved kind to Dirent objects
  • dir_iterator.zig: Return .unknown for DT_UNKNOWN entries, letting callers handle lazy stat

Why lstatat instead of fstatat? We use AT_SYMLINK_NOFOLLOW to preserve consistent behavior with normal filesystems - symlinks should be reported as symlinks, not as their target type. This matches Node.js behavior which uses lstat() for the DT_UNKNOWN fallback, and follows the lazy stat pattern established in PR #18172.

How did you verify your code works?

Testing:

  • Regression test: test/regression/issue/24007.test.ts
  • FUSE filesystem test: test/cli/run/glob-on-fuse.test.ts (reuses fuse-fs.py from PR Correctly handle unknown type in FileSystem.DirEntry.addEntry #18172, includes symlink verification)
  • All existing glob/readdir tests pass
  • Verified in Docker bind-mount environment:
    • Official Bun: 0 files
    • Patched Bun: 3 files

Performance: No impact on normal filesystems - the .unknown branch is only hit when d_type == DT_UNKNOWN. The lazy stat pattern avoids unnecessary syscalls by checking pattern match first.

Fixes oven-sh#24007

Some filesystems (bind mounts, FUSE, NFS) don't provide d_type in directory
entries, returning DT_UNKNOWN. This caused glob and recursive readdir to skip
entries entirely.

Added fstatat() fallback when d_type is unknown to determine actual file type,
matching Node.js/libuv behavior.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 5, 2026

Walkthrough

Resolve DT_UNKNOWN directory-entry kinds by performing non-following lazy stats (lstatat/fstatat) during traversal; propagate the resolved kind into NodeFS readdir recursion, GlobWalker traversal, and emitted Dirent.kind. Add regression and FUSE tests covering bind-mounted and DT_UNKNOWN behavior.

Changes

Cohort / File(s) Summary
Dir iterator annotation
src/bun.js/node/dir_iterator.zig
Add explicit entry_kind: Entry.Kind annotation and a comment about DT_UNKNOWN recommending lazy stat; no control-flow changes.
NodeFS readdir recursion
src/bun.js/node/node_fs.zig
For DT_UNKNOWN, perform lazy stat (lstatat/fstatat) to compute effective_kind; use it for recursion/enqueue and when emitting Dirent.kind. Adds path-length checks and error-safe stat handling (async & sync paths).
Glob traversal / DirEntry accessors
src/glob/GlobWalker.zig, .../DirEntryAccessor
Add lstatat usage to resolve DT_UNKNOWN before pattern matching or enqueueing recursion; handle files, directories, and symlinks (respecting follow_symlinks) uniformly after resolution.
Sys / Syscall API (new lstatat)
src/sys.zig, .../SyscallAccessor
Add pub fn lstatat(fd: bun.FileDescriptor, path: [:0]const u8) Maybe(bun.Stat) implemented via fstatat with AT_SYMLINK_NOFOLLOW on POSIX, with Windows fallback; expose through syscall/dir-entry accessors.
Tests — regression & FUSE
test/regression/issue/24007.test.ts, test/cli/run/glob-on-fuse.test.ts
Add regression test for issue #24007 (bind-mounted glob/readdir) and FUSE-based tests simulating DT_UNKNOWN; assert Bun.Glob, fs.globSync, fs.readdirSync, and withFileTypes behaviors (including symlink reporting).

Possibly related PRs

Suggested reviewers

  • pfgithub
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main fix: handling DT_UNKNOWN in dir_iterator for bind-mounted filesystems. It directly reflects the primary objective of the PR.
Linked Issues check ✅ Passed The PR fully addresses issue #24007 objectives: implements lazy lstatat() fallback for DT_UNKNOWN entries, ensures glob and recursive readdir work consistently across bind-mounted and normal filesystems, and includes comprehensive testing (regression test, FUSE filesystem test with symlink verification).
Out of Scope Changes check ✅ Passed All code changes are directly scoped to fixing DT_UNKNOWN handling: sys.zig (lstatat function), dir_iterator.zig (return .unknown), GlobWalker.zig (lazy stat for .unknown), node_fs.zig (recursive readdir handling), and test files validating the fix. No unrelated modifications detected.
Description check ✅ Passed The PR description comprehensively follows the template with both required sections fully populated: 'What does this PR do?' explains the fix with context and rationale; 'How did you verify your code works?' details testing approach and verification methods.

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


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Fix all issues with AI Agents 🤖
In @test/regression/issue/24007.test.ts:
- Around line 20-130: The test suite uses a sequential describe("issue #24007 -
glob with recursive patterns") containing multiple file-writing tests; make them
run concurrently by changing the test grouping or tests to concurrent: either
replace describe(...) with describe.concurrent(...) or mark each test(...) as
test.concurrent(...) so tests like the ones creating tempDir and calling
fs.globSync, Bun.Glob, and fs.readdirSync run in parallel; update the block
containing the tests (the describe and/or individual test declarations)
accordingly to use describe.concurrent or test.concurrent.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ce97887 and 08892b9.

📒 Files selected for processing (2)
  • src/bun.js/node/dir_iterator.zig
  • test/regression/issue/24007.test.ts
🧰 Additional context used
📓 Path-based instructions (7)
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Use bun:test with files that end in *.test.{ts,js,jsx,tsx,mjs,cjs}
Do not write flaky tests. Never wait for time to pass in tests; always wait for the condition to be met instead of using an arbitrary amount of time
Never use hardcoded port numbers in tests. Always use port: 0 to get a random port
Prefer concurrent tests over sequential tests using test.concurrent or describe.concurrent when multiple tests spawn processes or write files, unless it's very difficult to make them concurrent
When spawning Bun processes in tests, use bunExe and bunEnv from harness to ensure the same build of Bun is used and debug logging is silenced
Use -e flag for single-file tests when spawning Bun processes
Use tempDir() from harness to create temporary directories with files for multi-file tests instead of creating files manually
Prefer async/await over callbacks in tests
When callbacks must be used and it's just a single callback, use Promise.withResolvers to create a promise that can be resolved or rejected from a callback
Do not set a timeout on tests. Bun already has timeouts
Use Buffer.alloc(count, fill).toString() instead of 'A'.repeat(count) to create repetitive strings in tests, as ''.repeat is very slow in debug JavaScriptCore builds
Use describe blocks for grouping related tests
Always use await using or using to ensure proper resource cleanup in tests for APIs like Bun.listen, Bun.connect, Bun.spawn, Bun.serve, etc
Always check exit codes and test error scenarios in error tests
Use describe.each() for parameterized tests
Use toMatchSnapshot() for snapshot testing
Use beforeAll(), afterEach(), beforeEach() for setup/teardown in tests
Track resources (servers, clients) in arrays for cleanup in afterEach()

Files:

  • test/regression/issue/24007.test.ts
test/regression/issue/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

Regression tests for specific issues go in /test/regression/issue/${issueNumber}.test.ts. Do not put tests without issue numbers in the regression directory

Files:

  • test/regression/issue/24007.test.ts
**/*.test.ts?(x)

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.ts?(x): Never use bun test directly - always use bun bd test to run tests with debug build changes
For single-file tests, prefer -e flag over tempDir
For multi-file tests, prefer tempDir and Bun.spawn over single-file tests
Use normalizeBunSnapshot to normalize snapshot output of tests
Never write tests that check for 'panic', 'uncaught exception', or similar strings in test output
Use tempDir from harness to create temporary directories - do not use tmpdirSync or fs.mkdtempSync
When spawning processes in tests, expect stdout before expecting exit code for more useful error messages on test failure
Do not write flaky tests - do not use setTimeout in tests; instead await the condition to be met
Verify tests fail with USE_SYSTEM_BUN=1 bun test <file> and pass with bun bd test <file> - tests are invalid if they pass with USE_SYSTEM_BUN=1
Test files must end with .test.ts or .test.tsx
Avoid shell commands like find or grep in tests - use Bun's Glob and built-in tools instead

Files:

  • test/regression/issue/24007.test.ts
test/regression/issue/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Place regression tests for specific GitHub issues in test/regression/issue/${issueNumber}.test.ts with real issue numbers only

Files:

  • test/regression/issue/24007.test.ts
test/**/*.test.ts?(x)

📄 CodeRabbit inference engine (CLAUDE.md)

Always use port: 0 in tests - do not hardcode ports or use custom random port number functions

Files:

  • test/regression/issue/24007.test.ts
src/**/*.zig

📄 CodeRabbit inference engine (src/CLAUDE.md)

src/**/*.zig: Private fields in Zig are fully supported using the # prefix: struct { #foo: u32 };
Use decl literals in Zig for declaration initialization: const decl: Decl = .{ .binding = 0, .value = 0 };
Prefer @import at the bottom of the file (auto formatter will move them automatically)

Files:

  • src/bun.js/node/dir_iterator.zig
**/*.zig

📄 CodeRabbit inference engine (CLAUDE.md)

In Zig code, be careful with allocators and use defer for cleanup

Files:

  • src/bun.js/node/dir_iterator.zig
🧠 Learnings (16)
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/regression/issue/**/*.test.ts : Regression tests for specific issues go in `/test/regression/issue/${issueNumber}.test.ts`. Do not put tests without issue numbers in the regression directory

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `tempDir()` from harness to create temporary directories with files for multi-file tests instead of creating files manually

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to test/regression/issue/*.test.ts : Place regression tests for specific GitHub issues in `test/regression/issue/${issueNumber}.test.ts` with real issue numbers only

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Avoid shell commands like `find` or `grep` in tests - use Bun's Glob and built-in tools instead

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : For multi-file tests, prefer `tempDir` and `Bun.spawn` over single-file tests

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Use `tempDir` from `harness` to create temporary directories - do not use `tmpdirSync` or `fs.mkdtempSync`

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : For single-file tests, prefer `-e` flag over `tempDir`

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Use `normalizeBunSnapshot` to normalize snapshot output of tests

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*-fixture.ts : Test files that spawn Bun processes should end in `*-fixture.ts` to identify them as test fixtures and not tests themselves

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-10-19T02:44:46.354Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/context-propagation.test.ts:1-1
Timestamp: 2025-10-19T02:44:46.354Z
Learning: In the Bun repository, standalone packages under packages/ (e.g., bun-vscode, bun-inspector-protocol, bun-plugin-yaml, bun-plugin-svelte, bun-debug-adapter-protocol, bun-otel) co-locate their tests with package source code using *.test.ts files. This follows standard npm/monorepo patterns. The test/ directory hierarchy (test/js/bun/, test/cli/, test/js/node/) is reserved for testing Bun's core runtime APIs and built-in functionality, not standalone packages.

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` with files that end in `*.test.{ts,js,jsx,tsx,mjs,cjs}`

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `-e` flag for single-file tests when spawning Bun processes

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-09-12T18:16:50.754Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 22606
File: src/glob/GlobWalker.zig:449-452
Timestamp: 2025-09-12T18:16:50.754Z
Learning: For Bun codebase: prefer using `std.fs.path.sep` over manual platform separator detection, and use `bun.strings.lastIndexOfChar` instead of `std.mem.lastIndexOfScalar` for string operations.

Applied to files:

  • src/bun.js/node/dir_iterator.zig
📚 Learning: 2025-10-29T19:05:25.638Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 24063
File: packages/bun-types/telemetry.d.ts:252-261
Timestamp: 2025-10-29T19:05:25.638Z
Learning: In packages/bun-types/telemetry.d.ts and src/telemetry/telemetry.zig, the naming inconsistency between `kind` (public API) and `type` (internal implementation) is intentional. The public API uses `InstrumentInfo.kind: InstrumentKind` with string values ("http", "fetch", etc.) for developer ergonomics, while the internal implementation uses `InstrumentRecord.type: InstrumentType` with numeric enum (0-6) for O(1) dispatch performance. This dual naming provides both ergonomic strings externally and efficient numeric dispatch internally without compromising either developer experience or runtime performance.

Applied to files:

  • src/bun.js/node/dir_iterator.zig
📚 Learning: 2026-01-05T16:32:07.551Z
Learnt from: alii
Repo: oven-sh/bun PR: 25474
File: src/bun.js/event_loop/Sigusr1Handler.zig:0-0
Timestamp: 2026-01-05T16:32:07.551Z
Learning: In Zig codebases (e.g., Bun), treat std.posix.sigaction as returning void and do not perform runtime error handling for its failure. The Zig standard library views sigaction failures as programmer errors (unreachable) because they only occur with invalid signals like SIGKILL/SIGSTOP. Apply this pattern across Zig files that call sigaction (e.g., crash_handler.zig, main.zig, filter_run.zig, process.zig) and ensure failures are not handled as recoverable errors; prefer reaching an explicit unreachable/compile-time assumption when such failures are detected.

Applied to files:

  • src/bun.js/node/dir_iterator.zig
🔇 Additional comments (9)
src/bun.js/node/dir_iterator.zig (1)

165-179: Excellent fallback implementation for DT_UNKNOWN handling.

The fstatat() fallback correctly addresses the issue with bind-mounted and FUSE filesystems that don't populate d_type. The implementation is sound:

  • Type annotation follows Zig best practices
  • Comment clearly explains when and why the fallback is needed
  • Graceful degradation to Entry.Kind.unknown if fstatat fails
  • The pointer cast at line 175 is safe because name points to the null-terminated data from linux_entry.name

This matches Node.js/libuv behavior and resolves the glob recursion failures reported in issue #24007.

test/regression/issue/24007.test.ts (8)

1-14: LGTM! Excellent documentation.

The header comment clearly explains the issue, root cause, fix, and testing limitations. The note about requiring Docker bind mounts for full DT_UNKNOWN verification is appropriate.


15-18: LGTM!

Imports follow coding guidelines correctly: uses bun:test, tempDir from harness, and appropriate Node.js standard library modules.


21-41: LGTM!

Test correctly validates recursive glob pattern matching. Uses tempDir with using for proper cleanup, creates appropriate test structure, and verifies both presence and count of matched files.


43-57: LGTM!

Test properly validates scoped recursive glob patterns with both positive (files in scope) and negative (files out of scope) assertions. Good test coverage.


59-73: LGTM!

Test correctly validates non-recursive glob patterns by verifying direct children are matched while nested files are properly excluded.


75-90: LGTM!

Test provides good coverage of Bun's native Glob API alongside fs.globSync tests. Correctly uses scanSync with cwd option.


92-107: LGTM!

Test thoroughly validates recursive directory reading with multiple nesting levels. Good coverage of the fs.readdirSync recursive option.


109-129: LGTM with a minor observation.

Test correctly validates directory entry types and the withFileTypes option. The logic at line 120 using parentPath?.includes("subdir") works correctly for this test setup, though it's slightly fragile. A more precise approach would be to construct the expected path using path.join, but the current implementation is acceptable for this regression test.

Copy link
Collaborator

@Jarred-Sumner Jarred-Sumner left a comment

Choose a reason for hiding this comment

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

This is going to be a huge performance hit. Can you point to where Node.js does this in libuv? Can you paste the output from the getdents64 struct in one of these filesystem mounts.

Comment on lines 11 to 13
* Note: This test verifies recursive glob patterns work correctly. Full verification
* of the DT_UNKNOWN fallback requires testing in a Docker environment with bind mounts
* as described in the original issue.
Copy link
Contributor

Choose a reason for hiding this comment

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

There is a test for similar behavior in #18172 that you might be able to adopt

Copy link
Author

Choose a reason for hiding this comment

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

this is great. thank you

Fixes oven-sh#24007

Some filesystems (Docker bind mounts via sshfs/FUSE, NFS, etc.) return
DT_UNKNOWN for d_type in directory entries. This caused glob patterns
like **/*.ts to return 0 results because unknown entries were being
skipped.

Changes:
- dir_iterator.zig: Return .unknown for DT_UNKNOWN (unchanged behavior)
- GlobWalker.zig: Handle .unknown with lazy stat - check pattern match
  first, then fstatat() only if needed (follows PR oven-sh#18172 pattern)
- node_fs.zig: Handle .unknown in recursive readdir operations

The lazy stat approach avoids performance regression on filesystems that
do provide d_type by only calling fstatat() when:
1. The entry type is unknown AND
2. The filename matches the glob pattern (for GlobWalker)
3. We need to recurse into it (for readdir)
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @src/bun.js/node/node_fs.zig:
- Around line 4649-4665: The async DT_UNKNOWN branch should set an effective
kind and use it when constructing the Dirent so callers see the resolved type:
introduce a local effective_kind initialized to current.kind, and inside the
.unknown switch when bun.sys.fstatat returns .result compute real_kind via
bun.sys.kindFromMode(st.mode), assign effective_kind = real_kind (while still
using real_kind or real_kind==.directory or .sym_link to decide
async_task.enqueue(name_to_copy)); then ensure the later Dirent construction
(bun.jsc.Node.Dirent) uses effective_kind instead of current.kind so
isDirectory()/isSymbolicLink() reflect the resolved value.
- Around line 4807-4822: The DT_UNKNOWN handling logic is duplicated between
sync and async paths; extract a small helper (e.g., shouldRecurseUnknown(fd,
current, name_to_copy) -> bool) that encapsulates the bun.MAX_PATH_BYTES check,
bun.sys.fstatat call, bun.sys.kindFromMode check for .directory or .sym_link,
and silent error handling, then replace the duplicated blocks in both branches
to call this helper and call stack.writeItem(basename_allocator.dupeZ(...)) only
when it returns true to preserve existing behavior.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 08892b9 and 7379c24.

📒 Files selected for processing (3)
  • src/bun.js/node/dir_iterator.zig
  • src/bun.js/node/node_fs.zig
  • src/glob/GlobWalker.zig
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.zig

📄 CodeRabbit inference engine (src/CLAUDE.md)

src/**/*.zig: Private fields in Zig are fully supported using the # prefix: struct { #foo: u32 };
Use decl literals in Zig for declaration initialization: const decl: Decl = .{ .binding = 0, .value = 0 };
Prefer @import at the bottom of the file (auto formatter will move them automatically)

Files:

  • src/bun.js/node/dir_iterator.zig
  • src/bun.js/node/node_fs.zig
  • src/glob/GlobWalker.zig
**/*.zig

📄 CodeRabbit inference engine (CLAUDE.md)

In Zig code, be careful with allocators and use defer for cleanup

Files:

  • src/bun.js/node/dir_iterator.zig
  • src/bun.js/node/node_fs.zig
  • src/glob/GlobWalker.zig
🧠 Learnings (2)
📚 Learning: 2025-10-29T19:05:25.638Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 24063
File: packages/bun-types/telemetry.d.ts:252-261
Timestamp: 2025-10-29T19:05:25.638Z
Learning: In packages/bun-types/telemetry.d.ts and src/telemetry/telemetry.zig, the naming inconsistency between `kind` (public API) and `type` (internal implementation) is intentional. The public API uses `InstrumentInfo.kind: InstrumentKind` with string values ("http", "fetch", etc.) for developer ergonomics, while the internal implementation uses `InstrumentRecord.type: InstrumentType` with numeric enum (0-6) for O(1) dispatch performance. This dual naming provides both ergonomic strings externally and efficient numeric dispatch internally without compromising either developer experience or runtime performance.

Applied to files:

  • src/bun.js/node/dir_iterator.zig
📚 Learning: 2026-01-05T16:32:07.551Z
Learnt from: alii
Repo: oven-sh/bun PR: 25474
File: src/bun.js/event_loop/Sigusr1Handler.zig:0-0
Timestamp: 2026-01-05T16:32:07.551Z
Learning: In Zig codebases (e.g., Bun), treat std.posix.sigaction as returning void and do not perform runtime error handling for its failure. The Zig standard library views sigaction failures as programmer errors (unreachable) because they only occur with invalid signals like SIGKILL/SIGSTOP. Apply this pattern across Zig files that call sigaction (e.g., crash_handler.zig, main.zig, filter_run.zig, process.zig) and ensure failures are not handled as recoverable errors; prefer reaching an explicit unreachable/compile-time assumption when such failures are detected.

Applied to files:

  • src/bun.js/node/dir_iterator.zig
  • src/bun.js/node/node_fs.zig
  • src/glob/GlobWalker.zig
🔇 Additional comments (2)
src/glob/GlobWalker.zig (1)

905-988: LGTM! Clean implementation of lazy stat fallback for DT_UNKNOWN entries.

The approach correctly:

  1. Pre-filters with matchPatternImpl before incurring stat overhead
  2. Uses arena allocator for the null-terminated name (consistent with line 645's pattern)
  3. Silently skips unstat-able entries which is appropriate for unknown types
  4. Routes to the correct handling logic for each discovered type

The duplicated handling logic for file/directory/symlink is acceptable here for clarity, and the symlink logic correctly mirrors the original behavior through the if/else if structure.

src/bun.js/node/dir_iterator.zig (1)

165-177: Good documentation addition.

The explicit type annotation and comment clarify the contract: this iterator reports .unknown for DT_UNKNOWN entries, and callers are responsible for the lazy stat fallback. This aligns well with the handling added in GlobWalker.zig and node_fs.zig.

Comment on lines 4807 to 4822
// DT_UNKNOWN for d_type. Use fstatat to determine if it's a directory.
.unknown => {
if (current.name.len + 1 + name_to_copy.len > bun.MAX_PATH_BYTES) break :enqueue;

// Lazy stat to determine if this is a directory we need to recurse into
const stat_result = bun.sys.fstatat(fd, current.name.sliceAssumeZ());
switch (stat_result) {
.result => |st| {
const real_kind = bun.sys.kindFromMode(st.mode);
if (real_kind == .directory or real_kind == .sym_link) {
stack.writeItem(basename_allocator.dupeZ(u8, name_to_copy) catch break :enqueue) catch break :enqueue;
}
},
.err => {}, // Skip entries we can't stat
}
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Sync DT_UNKNOWN handling mirrors async; consider sharing helper to avoid duplication

The synchronous recursive readdir now mirrors the async path’s .unknown handling with bun.sys.fstatat and the same MAX_PATH guard, which keeps behavior consistent across flavors.

Given the near-identical logic between the async and sync branches (including the “directory or symlink” check and silent error skip), consider extracting a small helper (e.g., shouldRecurseUnknown(fd, current, name_to_copy) returning a bool) to reduce duplication and keep future changes to DT_UNKNOWN handling in one place.

🤖 Prompt for AI Agents
In @src/bun.js/node/node_fs.zig around lines 4807 - 4822, The DT_UNKNOWN
handling logic is duplicated between sync and async paths; extract a small
helper (e.g., shouldRecurseUnknown(fd, current, name_to_copy) -> bool) that
encapsulates the bun.MAX_PATH_BYTES check, bun.sys.fstatat call,
bun.sys.kindFromMode check for .directory or .sym_link, and silent error
handling, then replace the duplicated blocks in both branches to call this
helper and call stack.writeItem(basename_allocator.dupeZ(...)) only when it
returns true to preserve existing behavior.

@mmitchellg5
Copy link
Author

mmitchellg5 commented Jan 7, 2026

This is going to be a huge performance hit. Can you point to where Node.js does this in libuv? Can you paste the output from the getdents64 struct in one of these filesystem mounts.

Node.js handles UV_DIRENT_UNKNOWN with an lstat() fallback in lib/internal/fs/utils.js lines 251-269 and 308-311.

This implementation follows the same pattern but is more efficient for glob because we:

  1. Check if the filename matches the pattern FIRST
  2. Only call fstatat() if the name matches
    This means on normal filesystems: zero overhead. On FUSE/bind mounts: only stats entries that might match.

  1. getdents64 output from Docker bind mount:

getdents64(3, [> {d_ino=4294967295, d_off=1, d_reclen=24, d_type=DT_UNKNOWN, d_name="."},> {d_ino=4294967295, d_off=2, d_reclen=24, d_type=DT_UNKNOWN, d_name=".."}, > {d_ino=4294967295, d_off=3, d_reclen=24, d_type=DT_UNKNOWN, d_name="api"},> {d_ino=4294967295, d_off=4, d_reclen=32, d_type=DT_UNKNOWN, d_name="index.ts"}> ], 32768) = 104>

All entries return d_type=DT_UNKNOWN on Docker bind mounts (via Colima/sshfs on macOS).

Adds test/cli/run/glob-on-fuse.test.ts which uses the same Python FUSE
filesystem from PR oven-sh#18172 to verify glob and readdir work correctly when
d_type returns DT_UNKNOWN.

Tests:
- Bun.Glob.scanSync on FUSE mount
- fs.globSync on FUSE mount
- fs.readdirSync on FUSE mount
- fs.readdirSync with withFileTypes on FUSE mount

Thanks to @190n for the suggestion.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @test/cli/run/glob-on-fuse.test.ts:
- Line 15: Add a brief inline comment next to the tmpdirSync call explaining why
tmpdirSync is used instead of harness.tempDir: note that tempDir requires
supplying a file-tree structure but this test needs an empty directory to act as
a FUSE mountpoint, so tmpdirSync is intentionally used for that purpose.
- Around line 27-33: The polling loop that waits for join(mountpoint, "main.js")
should also detect if the spawned Python process has already exited to fail
faster; update the loop that currently checks fs.existsSync(join(mountpoint,
"main.js")) and tries < 250 to additionally await or inspect
pythonProcess.exited (or the process exit code/promise) each iteration and, if
it has resolved with a non-zero/failed exit, throw or fail the test immediately
instead of continuing to poll. Keep the same polling/backoff behavior but break
and surface the Python process error when pythonProcess.exited indicates the
process finished with an error.
- Around line 14-52: The finally block in withFuseMount currently can throw a
cleanup error (the catch inside finally rethrows e) and thus swallow any
original exception from the try block; change the pattern to capture the
original error (e.g., store it in a variable before entering cleanup) and after
performing the unmount/cleanup logic rethrow the original error if it existed,
or if cleanup should be non-fatal, log the cleanup failure instead of throwing;
focus changes around withFuseMount, the pythonProcess handling, the umount
spawn/exited wait and the inner catch so the original exception is preserved (or
the cleanup error only logged) rather than lost.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7379c24 and 5c69ef8.

📒 Files selected for processing (2)
  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.test.ts?(x)

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.ts?(x): Never use bun test directly - always use bun bd test to run tests with debug build changes
For single-file tests, prefer -e flag over tempDir
For multi-file tests, prefer tempDir and Bun.spawn over single-file tests
Use normalizeBunSnapshot to normalize snapshot output of tests
Never write tests that check for 'panic', 'uncaught exception', or similar strings in test output
Use tempDir from harness to create temporary directories - do not use tmpdirSync or fs.mkdtempSync
When spawning processes in tests, expect stdout before expecting exit code for more useful error messages on test failure
Do not write flaky tests - do not use setTimeout in tests; instead await the condition to be met
Verify tests fail with USE_SYSTEM_BUN=1 bun test <file> and pass with bun bd test <file> - tests are invalid if they pass with USE_SYSTEM_BUN=1
Test files must end with .test.ts or .test.tsx
Avoid shell commands like find or grep in tests - use Bun's Glob and built-in tools instead

Files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
test/**/*.test.ts?(x)

📄 CodeRabbit inference engine (CLAUDE.md)

Always use port: 0 in tests - do not hardcode ports or use custom random port number functions

Files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Use bun bd test <...test file> to run tests with compiled code changes. Do not use bun test as it will not include your changes.
Use bun:test for files ending in *.test.{ts,js,jsx,tsx,mjs,cjs}. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, use bun bd <file> instead of bun bd test <file> since they expect exit code 0.
Do not set a timeout on tests. Bun already has timeouts built-in.

Files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
test/regression/issue/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Place regression tests for specific GitHub issues in test/regression/issue/${issueNumber}.test.ts with real issue numbers only

Files:

  • test/regression/issue/24007.test.ts
🧠 Learnings (19)
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Avoid shell commands like `find` or `grep` in tests - use Bun's Glob and built-in tools instead

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : For multi-file tests, prefer `tempDir` and `Bun.spawn` over single-file tests

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun bd test <...test file>` to run tests with compiled code changes. Do not use `bun test` as it will not include your changes.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` for files ending in `*.test.{ts,js,jsx,tsx,mjs,cjs}`. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, use `bun bd <file>` instead of `bun bd test <file>` since they expect exit code 0.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*-fixture.ts : Test files that spawn Bun processes should end in `*-fixture.ts` to identify them as test fixtures rather than tests themselves.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Use `normalizeBunSnapshot` to normalize snapshot output of tests

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Verify tests fail with `USE_SYSTEM_BUN=1 bun test <file>` and pass with `bun bd test <file>` - tests are invalid if they pass with USE_SYSTEM_BUN=1

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Never use `bun test` directly - always use `bun bd test` to run tests with debug build changes

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-10-26T01:32:04.844Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Use `tempDir` from `harness` to create temporary directories - do not use `tmpdirSync` or `fs.mkdtempSync`

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2025-10-19T02:44:46.354Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/context-propagation.test.ts:1-1
Timestamp: 2025-10-19T02:44:46.354Z
Learning: In the Bun repository, standalone packages under packages/ (e.g., bun-vscode, bun-inspector-protocol, bun-plugin-yaml, bun-plugin-svelte, bun-debug-adapter-protocol, bun-otel) co-locate their tests with package source code using *.test.ts files. This follows standard npm/monorepo patterns. The test/ directory hierarchy (test/js/bun/, test/cli/, test/js/node/) is reserved for testing Bun's core runtime APIs and built-in functionality, not standalone packages.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to test/regression/issue/*.test.ts : Place regression tests for specific GitHub issues in `test/regression/issue/${issueNumber}.test.ts` with real issue numbers only

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Organize regression tests for specific issues in `/test/regression/issue/${issueNumber}.test.ts`. Do not place regression tests in the regression directory if there is no associated issue number.

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : For single-file tests, prefer `-e` flag over `tempDir`

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Prefer concurrent tests over sequential tests. Use `test.concurrent` or `describe.concurrent` when multiple tests spawn processes or write files, unless it is very difficult to make them concurrent.

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Do not write flaky tests - do not use `setTimeout` in tests; instead await the condition to be met

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-11-07T17:28:51.204Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24423
File: test/js/node/path/browserify.test.js:472-475
Timestamp: 2025-11-07T17:28:51.204Z
Learning: In test files under test/js/node/path/, markovejnovic prefers functional programming patterns (e.g., reduce, map, filter chains) over imperative loops for clarity and readability.

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : When spawning processes in tests, expect stdout before expecting exit code for more useful error messages on test failure

Applied to files:

  • test/regression/issue/24007.test.ts
🧬 Code graph analysis (2)
test/cli/run/glob-on-fuse.test.ts (2)
test/harness.ts (1)
  • tmpdirSync (1211-1213)
scripts/glob-sources.mjs (1)
  • glob (11-11)
test/regression/issue/24007.test.ts (2)
test/harness.ts (1)
  • tempDir (277-284)
scripts/glob-sources.mjs (1)
  • glob (11-11)
🪛 Biome (2.1.2)
test/cli/run/glob-on-fuse.test.ts

[error] 48-48: Unsafe usage of 'throw'.

'throw' in 'finally' overwrites the control flow statements inside 'try' and 'catch'.

(lint/correctness/noUnsafeFinally)

🔇 Additional comments (3)
test/regression/issue/24007.test.ts (1)

1-18: LGTM! Good documentation and imports.

The header documentation clearly explains the issue, the fix, and references related tests. Imports correctly use tempDir from harness as required by coding guidelines.

test/cli/run/glob-on-fuse.test.ts (2)

1-12: LGTM! Clear documentation and appropriate imports.

Good header documentation linking to the related issue and PR. The skipIf(!isLinux) is appropriate since FUSE is Linux-specific.


54-112: LGTM! Comprehensive FUSE filesystem tests.

The tests cover the key scenarios for DT_UNKNOWN handling:

  • Bun.Glob.scanSync pattern matching
  • fs.globSync compatibility
  • Basic fs.readdirSync
  • fs.readdirSync with withFileTypes validating isFile() and isSymbolicLink()

The 10-second timeouts are appropriate for FUSE operations that may be slower than native filesystem calls.

When d_type is unknown and we stat to determine the real kind,
now use the resolved kind when constructing Dirent objects so
callers see correct isDirectory()/isFile()/isSymbolicLink() values
instead of 'unknown'.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @test/regression/issue/24007.test.ts:
- Around line 108-129: The parentPath check for nestedFile is too loose—replace
the `.parentPath?.includes("subdir")` check with a strict path-segment test:
import/require Node's path utilities and ensure parentPath is split by path.sep
(or use path.basename/endsWith on a constructed relative path) and assert that
one of the path segments equals "subdir" (apply this change to the `nestedFile`
find predicate where `results.find((d) => d.name === "file.txt" &&
d.parentPath?.includes("subdir"))` is used) so the test only matches the
intended subdirectory.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5c69ef8 and b23d261.

📒 Files selected for processing (1)
  • test/regression/issue/24007.test.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.test.ts?(x)

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.ts?(x): Never use bun test directly - always use bun bd test to run tests with debug build changes
For single-file tests, prefer -e flag over tempDir
For multi-file tests, prefer tempDir and Bun.spawn over single-file tests
Use normalizeBunSnapshot to normalize snapshot output of tests
Never write tests that check for 'panic', 'uncaught exception', or similar strings in test output
Use tempDir from harness to create temporary directories - do not use tmpdirSync or fs.mkdtempSync
When spawning processes in tests, expect stdout before expecting exit code for more useful error messages on test failure
Do not write flaky tests - do not use setTimeout in tests; instead await the condition to be met
Verify tests fail with USE_SYSTEM_BUN=1 bun test <file> and pass with bun bd test <file> - tests are invalid if they pass with USE_SYSTEM_BUN=1
Test files must end with .test.ts or .test.tsx
Avoid shell commands like find or grep in tests - use Bun's Glob and built-in tools instead

Files:

  • test/regression/issue/24007.test.ts
test/regression/issue/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Place regression tests for specific GitHub issues in test/regression/issue/${issueNumber}.test.ts with real issue numbers only

Files:

  • test/regression/issue/24007.test.ts
test/**/*.test.ts?(x)

📄 CodeRabbit inference engine (CLAUDE.md)

Always use port: 0 in tests - do not hardcode ports or use custom random port number functions

Files:

  • test/regression/issue/24007.test.ts
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Use bun bd test <...test file> to run tests with compiled code changes. Do not use bun test as it will not include your changes.
Use bun:test for files ending in *.test.{ts,js,jsx,tsx,mjs,cjs}. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, use bun bd <file> instead of bun bd test <file> since they expect exit code 0.
Do not set a timeout on tests. Bun already has timeouts built-in.

Files:

  • test/regression/issue/24007.test.ts
🧠 Learnings (17)
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to test/regression/issue/*.test.ts : Place regression tests for specific GitHub issues in `test/regression/issue/${issueNumber}.test.ts` with real issue numbers only

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : For multi-file tests, prefer `tempDir` and `Bun.spawn` over single-file tests

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Avoid shell commands like `find` or `grep` in tests - use Bun's Glob and built-in tools instead

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Organize regression tests for specific issues in `/test/regression/issue/${issueNumber}.test.ts`. Do not place regression tests in the regression directory if there is no associated issue number.

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : For single-file tests, prefer `-e` flag over `tempDir`

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Use `tempDir` from `harness` to create temporary directories - do not use `tmpdirSync` or `fs.mkdtempSync`

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun bd test <...test file>` to run tests with compiled code changes. Do not use `bun test` as it will not include your changes.

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Use `normalizeBunSnapshot` to normalize snapshot output of tests

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*-fixture.ts : Test files that spawn Bun processes should end in `*-fixture.ts` to identify them as test fixtures rather than tests themselves.

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-11-07T17:28:51.204Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24423
File: test/js/node/path/browserify.test.js:472-475
Timestamp: 2025-11-07T17:28:51.204Z
Learning: In test files under test/js/node/path/, markovejnovic prefers functional programming patterns (e.g., reduce, map, filter chains) over imperative loops for clarity and readability.

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Do not write flaky tests - do not use `setTimeout` in tests; instead await the condition to be met

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` for files ending in `*.test.{ts,js,jsx,tsx,mjs,cjs}`. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, use `bun bd <file>` instead of `bun bd test <file>` since they expect exit code 0.

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Prefer concurrent tests over sequential tests. Use `test.concurrent` or `describe.concurrent` when multiple tests spawn processes or write files, unless it is very difficult to make them concurrent.

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-10-19T02:44:46.354Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/context-propagation.test.ts:1-1
Timestamp: 2025-10-19T02:44:46.354Z
Learning: In the Bun repository, standalone packages under packages/ (e.g., bun-vscode, bun-inspector-protocol, bun-plugin-yaml, bun-plugin-svelte, bun-debug-adapter-protocol, bun-otel) co-locate their tests with package source code using *.test.ts files. This follows standard npm/monorepo patterns. The test/ directory hierarchy (test/js/bun/, test/cli/, test/js/node/) is reserved for testing Bun's core runtime APIs and built-in functionality, not standalone packages.

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-10-26T01:32:04.844Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Verify tests fail with `USE_SYSTEM_BUN=1 bun test <file>` and pass with `bun bd test <file>` - tests are invalid if they pass with USE_SYSTEM_BUN=1

Applied to files:

  • test/regression/issue/24007.test.ts
🔇 Additional comments (6)
test/regression/issue/24007.test.ts (6)

1-18: LGTM! Well-documented test setup.

The file header clearly explains the issue, fix approach, and references the related FUSE test. Imports are appropriate for the test scenarios.


19-40: LGTM! Comprehensive recursive glob test.

The test correctly validates that recursive glob patterns find files at all nesting levels. Uses tempDir properly and validates both individual matches and the exact count.


42-56: LGTM! Validates subdirectory filtering.

The test correctly verifies that glob patterns with directory prefixes filter appropriately, including both positive and negative cases.


58-72: LGTM! Validates non-recursive glob patterns.

The test ensures that top-level patterns (without **) correctly exclude nested files, confirming the fix preserves the distinction between recursive and non-recursive patterns.


74-89: LGTM! Tests Bun.Glob API directly.

Good coverage of the Bun.Glob class in addition to fs.globSync, ensuring the fix works across both glob APIs.


91-106: LGTM! Validates recursive readdir.

The test ensures fs.readdirSync with recursive: true discovers files at all nesting levels, which also relies on the DT_UNKNOWN fix in directory iteration.

- Add comment explaining tmpdirSync usage (tempDir requires file tree)
- Check if Python process exited early in polling loop
- Preserve original error instead of swallowing in finally block
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @test/cli/run/glob-on-fuse.test.ts:
- Line 9: The import line in glob-on-fuse.test.ts includes unused symbols bunEnv
and bunExe; remove those from the import so only the actually used identifiers
(isLinux and tmpdirSync) are imported from "harness", and run a quick search for
any remaining references to bunEnv or bunExe in this file to ensure no usages
remain.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b23d261 and 7107213.

📒 Files selected for processing (3)
  • src/bun.js/node/node_fs.zig
  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
🧰 Additional context used
📓 Path-based instructions (6)
src/**/*.zig

📄 CodeRabbit inference engine (src/CLAUDE.md)

src/**/*.zig: Private fields in Zig are fully supported using the # prefix: struct { #foo: u32 };
Use decl literals in Zig for declaration initialization: const decl: Decl = .{ .binding = 0, .value = 0 };
Prefer @import at the bottom of the file (auto formatter will move them automatically)

Files:

  • src/bun.js/node/node_fs.zig
**/*.zig

📄 CodeRabbit inference engine (CLAUDE.md)

In Zig code, be careful with allocators and use defer for cleanup

Files:

  • src/bun.js/node/node_fs.zig
**/*.test.ts?(x)

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.ts?(x): Never use bun test directly - always use bun bd test to run tests with debug build changes
For single-file tests, prefer -e flag over tempDir
For multi-file tests, prefer tempDir and Bun.spawn over single-file tests
Use normalizeBunSnapshot to normalize snapshot output of tests
Never write tests that check for 'panic', 'uncaught exception', or similar strings in test output
Use tempDir from harness to create temporary directories - do not use tmpdirSync or fs.mkdtempSync
When spawning processes in tests, expect stdout before expecting exit code for more useful error messages on test failure
Do not write flaky tests - do not use setTimeout in tests; instead await the condition to be met
Verify tests fail with USE_SYSTEM_BUN=1 bun test <file> and pass with bun bd test <file> - tests are invalid if they pass with USE_SYSTEM_BUN=1
Test files must end with .test.ts or .test.tsx
Avoid shell commands like find or grep in tests - use Bun's Glob and built-in tools instead

Files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
test/**/*.test.ts?(x)

📄 CodeRabbit inference engine (CLAUDE.md)

Always use port: 0 in tests - do not hardcode ports or use custom random port number functions

Files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Use bun bd test <...test file> to run tests with compiled code changes. Do not use bun test as it will not include your changes.
Use bun:test for files ending in *.test.{ts,js,jsx,tsx,mjs,cjs}. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, use bun bd <file> instead of bun bd test <file> since they expect exit code 0.
Do not set a timeout on tests. Bun already has timeouts built-in.

Files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
test/regression/issue/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Place regression tests for specific GitHub issues in test/regression/issue/${issueNumber}.test.ts with real issue numbers only

Files:

  • test/regression/issue/24007.test.ts
🧠 Learnings (32)
📚 Learning: 2025-10-29T19:05:25.638Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 24063
File: packages/bun-types/telemetry.d.ts:252-261
Timestamp: 2025-10-29T19:05:25.638Z
Learning: In packages/bun-types/telemetry.d.ts and src/telemetry/telemetry.zig, the naming inconsistency between `kind` (public API) and `type` (internal implementation) is intentional. The public API uses `InstrumentInfo.kind: InstrumentKind` with string values ("http", "fetch", etc.) for developer ergonomics, while the internal implementation uses `InstrumentRecord.type: InstrumentType` with numeric enum (0-6) for O(1) dispatch performance. This dual naming provides both ergonomic strings externally and efficient numeric dispatch internally without compromising either developer experience or runtime performance.

Applied to files:

  • src/bun.js/node/node_fs.zig
📚 Learning: 2025-09-12T18:16:50.754Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 22606
File: src/glob/GlobWalker.zig:449-452
Timestamp: 2025-09-12T18:16:50.754Z
Learning: For Bun codebase: prefer using `std.fs.path.sep` over manual platform separator detection, and use `bun.strings.lastIndexOfChar` instead of `std.mem.lastIndexOfScalar` for string operations.

Applied to files:

  • src/bun.js/node/node_fs.zig
📚 Learning: 2025-09-02T19:17:26.376Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 0
File: :0-0
Timestamp: 2025-09-02T19:17:26.376Z
Learning: In Bun's Zig codebase, when handling error unions where the same cleanup operation (like `rawFree`) needs to be performed regardless of success or failure, prefer using boolean folding with `else |err| switch (err)` over duplicating the cleanup call in multiple switch branches. This approach avoids code duplication while maintaining compile-time error checking.

Applied to files:

  • src/bun.js/node/node_fs.zig
📚 Learning: 2025-10-08T13:56:00.875Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 23373
File: src/bun.js/api/BunObject.zig:2514-2521
Timestamp: 2025-10-08T13:56:00.875Z
Learning: For Bun codebase: prefer using `bun.path` utilities (e.g., `bun.path.joinAbsStringBuf`, `bun.path.join`) over `std.fs.path` functions for path operations.

Applied to files:

  • src/bun.js/node/node_fs.zig
📚 Learning: 2026-01-05T16:32:07.551Z
Learnt from: alii
Repo: oven-sh/bun PR: 25474
File: src/bun.js/event_loop/Sigusr1Handler.zig:0-0
Timestamp: 2026-01-05T16:32:07.551Z
Learning: In Zig codebases (e.g., Bun), treat std.posix.sigaction as returning void and do not perform runtime error handling for its failure. The Zig standard library views sigaction failures as programmer errors (unreachable) because they only occur with invalid signals like SIGKILL/SIGSTOP. Apply this pattern across Zig files that call sigaction (e.g., crash_handler.zig, main.zig, filter_run.zig, process.zig) and ensure failures are not handled as recoverable errors; prefer reaching an explicit unreachable/compile-time assumption when such failures are detected.

Applied to files:

  • src/bun.js/node/node_fs.zig
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Avoid shell commands like `find` or `grep` in tests - use Bun's Glob and built-in tools instead

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : For multi-file tests, prefer `tempDir` and `Bun.spawn` over single-file tests

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun bd test <...test file>` to run tests with compiled code changes. Do not use `bun test` as it will not include your changes.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` for files ending in `*.test.{ts,js,jsx,tsx,mjs,cjs}`. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, use `bun bd <file>` instead of `bun bd test <file>` since they expect exit code 0.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*-fixture.ts : Test files that spawn Bun processes should end in `*-fixture.ts` to identify them as test fixtures rather than tests themselves.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Use `normalizeBunSnapshot` to normalize snapshot output of tests

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Verify tests fail with `USE_SYSTEM_BUN=1 bun test <file>` and pass with `bun bd test <file>` - tests are invalid if they pass with USE_SYSTEM_BUN=1

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Never use `bun test` directly - always use `bun bd test` to run tests with debug build changes

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2025-10-26T01:32:04.844Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2025-11-03T20:40:59.655Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24273
File: src/bun.js/bindings/JSValue.zig:545-586
Timestamp: 2025-11-03T20:40:59.655Z
Learning: In Bun's Zig codebase, JSErrors (returned as `bun.JSError!T`) must always be properly handled. Using `catch continue` or `catch { break; }` to silently suppress JSErrors is a bug. Errors should either be explicitly handled or propagated with `try`. This applies to snapshot serializer error handling where Jest's behavior is to throw when serializers throw.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-11-06T00:58:23.965Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24417
File: test/js/bun/spawn/spawn.test.ts:903-918
Timestamp: 2025-11-06T00:58:23.965Z
Learning: In Bun test files, `await using` with spawn() is appropriate for long-running processes that need guaranteed cleanup on scope exit or when explicitly testing disposal behavior. For short-lived processes that exit naturally (e.g., console.log scripts), the pattern `const proc = spawn(...); await proc.exited;` is standard and more common, as evidenced by 24 instances vs 4 `await using` instances in test/js/bun/spawn/spawn.test.ts.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Never write tests that check for 'panic', 'uncaught exception', or similar strings in test output

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-09-20T00:58:38.042Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 22568
File: test/js/valkey/valkey.test.ts:561-564
Timestamp: 2025-09-20T00:58:38.042Z
Learning: For test/js/valkey/valkey.test.ts, do not comment on synchronous throw assertions for async Redis methods (like ctx.redis.set(), ctx.redis.unsubscribe(), etc.) - Bun's Redis client implementation differs from Node.js and can throw synchronously even for async methods. The maintainer has explicitly requested to stop looking at this error pattern.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : When spawning processes in tests, expect stdout before expecting exit code for more useful error messages on test failure

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-11-08T04:06:33.198Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24491
File: test/js/bun/transpiler/declare-global.test.ts:17-17
Timestamp: 2025-11-08T04:06:33.198Z
Learning: In Bun test files, `await using` with Bun.spawn() is the preferred pattern for spawned processes regardless of whether they are short-lived or long-running. Do not suggest replacing `await using proc = Bun.spawn(...)` with `const proc = Bun.spawn(...); await proc.exited;`.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-09-20T00:57:56.685Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 22568
File: test/js/valkey/valkey.test.ts:268-276
Timestamp: 2025-09-20T00:57:56.685Z
Learning: For test/js/valkey/valkey.test.ts, do not comment on synchronous throw assertions for async Redis methods like ctx.redis.set() - the maintainer has explicitly requested to stop looking at this error pattern.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Use `tempDir` from `harness` to create temporary directories - do not use `tmpdirSync` or `fs.mkdtempSync`

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : For single-file tests, prefer `-e` flag over `tempDir`

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: When multi-file tests are required, use `tempDir()` from `harness` to create temporary test fixtures.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Use `tempDir()` from `harness` to create temporary directories with files in tests instead of manually creating them.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-10-19T02:44:46.354Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/context-propagation.test.ts:1-1
Timestamp: 2025-10-19T02:44:46.354Z
Learning: In the Bun repository, standalone packages under packages/ (e.g., bun-vscode, bun-inspector-protocol, bun-plugin-yaml, bun-plugin-svelte, bun-debug-adapter-protocol, bun-otel) co-locate their tests with package source code using *.test.ts files. This follows standard npm/monorepo patterns. The test/ directory hierarchy (test/js/bun/, test/cli/, test/js/node/) is reserved for testing Bun's core runtime APIs and built-in functionality, not standalone packages.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to test/regression/issue/*.test.ts : Place regression tests for specific GitHub issues in `test/regression/issue/${issueNumber}.test.ts` with real issue numbers only

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Organize regression tests for specific issues in `/test/regression/issue/${issueNumber}.test.ts`. Do not place regression tests in the regression directory if there is no associated issue number.

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-11-07T17:28:51.204Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24423
File: test/js/node/path/browserify.test.js:472-475
Timestamp: 2025-11-07T17:28:51.204Z
Learning: In test files under test/js/node/path/, markovejnovic prefers functional programming patterns (e.g., reduce, map, filter chains) over imperative loops for clarity and readability.

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Do not write flaky tests - do not use `setTimeout` in tests; instead await the condition to be met

Applied to files:

  • test/regression/issue/24007.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Prefer concurrent tests over sequential tests. Use `test.concurrent` or `describe.concurrent` when multiple tests spawn processes or write files, unless it is very difficult to make them concurrent.

Applied to files:

  • test/regression/issue/24007.test.ts
🧬 Code graph analysis (2)
test/cli/run/glob-on-fuse.test.ts (2)
test/harness.ts (1)
  • tmpdirSync (1211-1213)
scripts/glob-sources.mjs (1)
  • glob (11-11)
test/regression/issue/24007.test.ts (2)
test/harness.ts (1)
  • tempDir (277-284)
scripts/glob-sources.mjs (1)
  • glob (11-11)
🪛 Biome (2.1.2)
test/cli/run/glob-on-fuse.test.ts

[error] 58-58: Unsafe usage of 'throw'.

'throw' in 'finally' overwrites the control flow statements inside 'try' and 'catch'.

(lint/correctness/noUnsafeFinally)

🔇 Additional comments (4)
src/bun.js/node/node_fs.zig (1)

4630-4669: Async DT_UNKNOWN fallback and effective_kind propagation look correct

The .unknown branch’s bun.sys.fstatat fallback, guarded by the same MAX_PATH check as the known-kinds, correctly resolves real kind via bun.sys.kindFromMode(st.mode) and only enqueues directories/symlinks. Using a local effective_kind and wiring it into the bun.jsc.Node.Dirent construction means isDirectory() / isSymbolicLink() will now reflect the resolved type instead of staying .unknown, which aligns the observable behavior with the traversal logic. I don’t see correctness or resource‑handling issues here.

Also applies to: 4683-4687

test/regression/issue/24007.test.ts (1)

19-130: Regression coverage for bind‑mount / DT_UNKNOWN behavior looks solid

The tests exercise all relevant surfaces for this bug: fs.globSync with recursive and non‑recursive patterns, Bun.Glob.scanSync, and fs.readdirSync both with plain strings and withFileTypes (including validation of parentPath and Dirent type methods). They use tempDir correctly, run under describe.concurrent, and assert both positive and negative matches plus counts, so they should reliably catch regressions in the DT_UNKNOWN fallback logic without being flaky.

test/cli/run/glob-on-fuse.test.ts (2)

14-63: LGTM! Error handling pattern is correct.

The originalError tracking pattern correctly preserves original test failures while allowing cleanup errors to surface only when no test failure occurred. The static analysis warning at line 58 is a false positive—the conditional guard if (!originalError) ensures the cleanup error doesn't overwrite the original exception.

Previous review concerns (tmpdirSync usage, polling loop, exception preservation) have been properly addressed.


23-23: The fuse-fs.py fixture file already exists in the repository at test/cli/run/fuse-fs.py. No action is needed.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @test/cli/run/glob-on-fuse.test.ts:
- Line 77: Remove the explicit 10000 timeout arguments from the four test()
calls in test/cli/run/glob-on-fuse.test.ts (the tests at the commented positions
around lines 77, 90, 103, and 121); edit each test invocation that currently
passes 10000 as a third parameter so it only calls test(name, async () => { ...
}) (or the existing two-argument form) and rely on Bun's built-in timeout
handling instead.
- Around line 53-60: The catch/finally currently may throw inside the finally
block (the throw e when !originalError), which triggers static analysis; change
the flow to avoid throwing inside finally by capturing the caught error into a
local variable (e.g., let caughtError = e) and performing cleanup
(pythonProcess.kill and stderr logging) in the finally, then after the finally
re-throw: if (!originalError) throw caughtError; Ensure you reference the
existing variables pythonProcess, originalError, and the caught error variable
you introduce so behavior is identical but no throw occurs inside the finally.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7107213 and 04de59f.

📒 Files selected for processing (1)
  • test/cli/run/glob-on-fuse.test.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.test.ts?(x)

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.ts?(x): Never use bun test directly - always use bun bd test to run tests with debug build changes
For single-file tests, prefer -e flag over tempDir
For multi-file tests, prefer tempDir and Bun.spawn over single-file tests
Use normalizeBunSnapshot to normalize snapshot output of tests
Never write tests that check for 'panic', 'uncaught exception', or similar strings in test output
Use tempDir from harness to create temporary directories - do not use tmpdirSync or fs.mkdtempSync
When spawning processes in tests, expect stdout before expecting exit code for more useful error messages on test failure
Do not write flaky tests - do not use setTimeout in tests; instead await the condition to be met
Verify tests fail with USE_SYSTEM_BUN=1 bun test <file> and pass with bun bd test <file> - tests are invalid if they pass with USE_SYSTEM_BUN=1
Test files must end with .test.ts or .test.tsx
Avoid shell commands like find or grep in tests - use Bun's Glob and built-in tools instead

Files:

  • test/cli/run/glob-on-fuse.test.ts
test/**/*.test.ts?(x)

📄 CodeRabbit inference engine (CLAUDE.md)

Always use port: 0 in tests - do not hardcode ports or use custom random port number functions

Files:

  • test/cli/run/glob-on-fuse.test.ts
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Use bun bd test <...test file> to run tests with compiled code changes. Do not use bun test as it will not include your changes.
Use bun:test for files ending in *.test.{ts,js,jsx,tsx,mjs,cjs}. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, use bun bd <file> instead of bun bd test <file> since they expect exit code 0.
Do not set a timeout on tests. Bun already has timeouts built-in.

Files:

  • test/cli/run/glob-on-fuse.test.ts
🧠 Learnings (24)
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Avoid shell commands like `find` or `grep` in tests - use Bun's Glob and built-in tools instead

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : For multi-file tests, prefer `tempDir` and `Bun.spawn` over single-file tests

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun bd test <...test file>` to run tests with compiled code changes. Do not use `bun test` as it will not include your changes.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` for files ending in `*.test.{ts,js,jsx,tsx,mjs,cjs}`. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, use `bun bd <file>` instead of `bun bd test <file>` since they expect exit code 0.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*-fixture.ts : Test files that spawn Bun processes should end in `*-fixture.ts` to identify them as test fixtures rather than tests themselves.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Use `normalizeBunSnapshot` to normalize snapshot output of tests

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Verify tests fail with `USE_SYSTEM_BUN=1 bun test <file>` and pass with `bun bd test <file>` - tests are invalid if they pass with USE_SYSTEM_BUN=1

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Never use `bun test` directly - always use `bun bd test` to run tests with debug build changes

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-10-26T01:32:04.844Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-11-03T20:40:59.655Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24273
File: src/bun.js/bindings/JSValue.zig:545-586
Timestamp: 2025-11-03T20:40:59.655Z
Learning: In Bun's Zig codebase, JSErrors (returned as `bun.JSError!T`) must always be properly handled. Using `catch continue` or `catch { break; }` to silently suppress JSErrors is a bug. Errors should either be explicitly handled or propagated with `try`. This applies to snapshot serializer error handling where Jest's behavior is to throw when serializers throw.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Never write tests that check for 'panic', 'uncaught exception', or similar strings in test output

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-09-20T00:58:38.042Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 22568
File: test/js/valkey/valkey.test.ts:561-564
Timestamp: 2025-09-20T00:58:38.042Z
Learning: For test/js/valkey/valkey.test.ts, do not comment on synchronous throw assertions for async Redis methods (like ctx.redis.set(), ctx.redis.unsubscribe(), etc.) - Bun's Redis client implementation differs from Node.js and can throw synchronously even for async methods. The maintainer has explicitly requested to stop looking at this error pattern.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-11-06T00:58:23.965Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24417
File: test/js/bun/spawn/spawn.test.ts:903-918
Timestamp: 2025-11-06T00:58:23.965Z
Learning: In Bun test files, `await using` with spawn() is appropriate for long-running processes that need guaranteed cleanup on scope exit or when explicitly testing disposal behavior. For short-lived processes that exit naturally (e.g., console.log scripts), the pattern `const proc = spawn(...); await proc.exited;` is standard and more common, as evidenced by 24 instances vs 4 `await using` instances in test/js/bun/spawn/spawn.test.ts.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : When spawning processes in tests, expect stdout before expecting exit code for more useful error messages on test failure

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-09-20T00:57:56.685Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 22568
File: test/js/valkey/valkey.test.ts:268-276
Timestamp: 2025-09-20T00:57:56.685Z
Learning: For test/js/valkey/valkey.test.ts, do not comment on synchronous throw assertions for async Redis methods like ctx.redis.set() - the maintainer has explicitly requested to stop looking at this error pattern.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Use `tempDir` from `harness` to create temporary directories - do not use `tmpdirSync` or `fs.mkdtempSync`

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : For single-file tests, prefer `-e` flag over `tempDir`

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: When multi-file tests are required, use `tempDir()` from `harness` to create temporary test fixtures.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Use `tempDir()` from `harness` to create temporary directories with files in tests instead of manually creating them.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-11-08T04:06:33.198Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24491
File: test/js/bun/transpiler/declare-global.test.ts:17-17
Timestamp: 2025-11-08T04:06:33.198Z
Learning: In Bun test files, `await using` with Bun.spawn() is the preferred pattern for spawned processes regardless of whether they are short-lived or long-running. Do not suggest replacing `await using proc = Bun.spawn(...)` with `const proc = Bun.spawn(...); await proc.exited;`.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: When spawning Bun processes in tests, use `bunExe` and `bunEnv` from `harness` to ensure the same build of Bun is used and debug logging is silenced.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-11-24T18:37:11.466Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js} : Use `process.platform` and `process.arch` for platform detection; these values are inlined and dead-code eliminated at build time

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
📚 Learning: 2025-10-19T02:44:46.354Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/context-propagation.test.ts:1-1
Timestamp: 2025-10-19T02:44:46.354Z
Learning: In the Bun repository, standalone packages under packages/ (e.g., bun-vscode, bun-inspector-protocol, bun-plugin-yaml, bun-plugin-svelte, bun-debug-adapter-protocol, bun-otel) co-locate their tests with package source code using *.test.ts files. This follows standard npm/monorepo patterns. The test/ directory hierarchy (test/js/bun/, test/cli/, test/js/node/) is reserved for testing Bun's core runtime APIs and built-in functionality, not standalone packages.

Applied to files:

  • test/cli/run/glob-on-fuse.test.ts
🪛 Biome (2.1.2)
test/cli/run/glob-on-fuse.test.ts

[error] 58-58: Unsafe usage of 'throw'.

'throw' in 'finally' overwrites the control flow statements inside 'try' and 'catch'.

(lint/correctness/noUnsafeFinally)

expect(results.length).toBeGreaterThanOrEqual(1);
});
},
10000
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove explicit test timeouts.

The coding guidelines specify: "Do not set a timeout on tests. Bun already has timeouts built-in." Remove the 10000 timeout parameter from all four test cases and rely on Bun's default timeout handling.

As per coding guidelines, Bun's built-in timeout mechanism should handle even potentially slow FUSE mount operations.

🔧 Proposed fix
   test(
     "Bun.Glob.scanSync finds files on FUSE mount",
     async () => {
       await withFuseMount(async (mountpoint) => {
         const glob = new Bun.Glob("*.js");
         const results = Array.from(glob.scanSync({ cwd: mountpoint }));

         // fuse-fs.py provides main.js and main-symlink.js
         expect(results).toContain("main.js");
         expect(results.length).toBeGreaterThanOrEqual(1);
       });
-    },
-    10000
+    }
   );

   test(
     "fs.globSync finds files on FUSE mount",
     async () => {
       await withFuseMount(async (mountpoint) => {
         const results = fs.globSync("*.js", { cwd: mountpoint });

         expect(results).toContain("main.js");
         expect(results.length).toBeGreaterThanOrEqual(1);
       });
-    },
-    10000
+    }
   );

   test(
     "fs.readdirSync works on FUSE mount",
     async () => {
       await withFuseMount(async (mountpoint) => {
         const results = fs.readdirSync(mountpoint);

         expect(results).toContain("main.js");
         expect(results).toContain("main-symlink.js");
       });
-    },
-    10000
+    }
   );

   test(
     "fs.readdirSync with withFileTypes returns correct types on FUSE mount",
     async () => {
       await withFuseMount(async (mountpoint) => {
         const results = fs.readdirSync(mountpoint, { withFileTypes: true });

         const mainJs = results.find((d) => d.name === "main.js");
         expect(mainJs).toBeDefined();
         expect(mainJs!.isFile()).toBe(true);

         const symlink = results.find((d) => d.name === "main-symlink.js");
         expect(symlink).toBeDefined();
         expect(symlink!.isSymbolicLink()).toBe(true);
       });
-    },
-    10000
+    }
   );

Also applies to: 90-90, 103-103, 121-121

🤖 Prompt for AI Agents
In @test/cli/run/glob-on-fuse.test.ts at line 77, Remove the explicit 10000
timeout arguments from the four test() calls in
test/cli/run/glob-on-fuse.test.ts (the tests at the commented positions around
lines 77, 90, 103, and 121); edit each test invocation that currently passes
10000 as a third parameter so it only calls test(name, async () => { ... }) (or
the existing two-argument form) and rely on Bun's built-in timeout handling
instead.

mmitchellg5 and others added 5 commits January 7, 2026 14:30
- Restructure error handling to throw outside finally
- Keep 10000ms timeouts (matches run-file-on-fuse.test.ts pattern)
- Add comment explaining why timeouts are needed for FUSE cleanup
Added lstatat() to sys.zig - fstatat with AT_SYMLINK_NOFOLLOW flag.

When d_type is DT_UNKNOWN, we need lstatat (not fstatat) to correctly
identify symlinks as symlinks rather than following them to their target.
This preserves consistent behavior with normal filesystems where d_type
is available - symlinks should be reported as symlinks regardless of
whether the filesystem populates d_type.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @src/glob/GlobWalker.zig:
- Line 920: Replace the unconditional heap allocation for entry_name with a
stack-fallback: check the length of entry_name and if it fits a small fixed-size
buffer (e.g., 256 bytes) copy and null-terminate into that stack buffer and set
name_z to point at it; otherwise call this.walker.arena.allocator().dupeZ(u8,
entry_name) catch bun.outOfMemory() as before. Ensure any heap-allocated name_z
is freed appropriately after use and that all callers expecting a C-style
zero-terminated name still work; mirror the literal-ending optimization pattern
already used elsewhere (the conditional stack/heap allocation approach).
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a5a9573 and 545ce01.

📒 Files selected for processing (3)
  • src/bun.js/node/node_fs.zig
  • src/glob/GlobWalker.zig
  • src/sys.zig
🧰 Additional context used
📓 Path-based instructions (2)
**/*.zig

📄 CodeRabbit inference engine (CLAUDE.md)

In Zig code, be careful with allocators and use defer for cleanup

Files:

  • src/bun.js/node/node_fs.zig
  • src/sys.zig
  • src/glob/GlobWalker.zig
src/**/*.zig

📄 CodeRabbit inference engine (src/CLAUDE.md)

src/**/*.zig: Use the # prefix for private fields in Zig structs, e.g., struct { #foo: u32 };
Use Decl literals in Zig, e.g., const decl: Decl = .{ .binding = 0, .value = 0 };
Place @import statements at the bottom of the file in Zig (auto formatter will handle positioning)
Never use @import() inline inside functions in Zig; always place imports at the bottom of the file or containing struct

Files:

  • src/bun.js/node/node_fs.zig
  • src/sys.zig
  • src/glob/GlobWalker.zig
🧠 Learnings (6)
📚 Learning: 2025-10-29T19:05:25.638Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 24063
File: packages/bun-types/telemetry.d.ts:252-261
Timestamp: 2025-10-29T19:05:25.638Z
Learning: In packages/bun-types/telemetry.d.ts and src/telemetry/telemetry.zig, the naming inconsistency between `kind` (public API) and `type` (internal implementation) is intentional. The public API uses `InstrumentInfo.kind: InstrumentKind` with string values ("http", "fetch", etc.) for developer ergonomics, while the internal implementation uses `InstrumentRecord.type: InstrumentType` with numeric enum (0-6) for O(1) dispatch performance. This dual naming provides both ergonomic strings externally and efficient numeric dispatch internally without compromising either developer experience or runtime performance.

Applied to files:

  • src/bun.js/node/node_fs.zig
📚 Learning: 2025-09-12T18:16:50.754Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 22606
File: src/glob/GlobWalker.zig:449-452
Timestamp: 2025-09-12T18:16:50.754Z
Learning: For Bun codebase: prefer using `std.fs.path.sep` over manual platform separator detection, and use `bun.strings.lastIndexOfChar` instead of `std.mem.lastIndexOfScalar` for string operations.

Applied to files:

  • src/bun.js/node/node_fs.zig
📚 Learning: 2025-09-02T19:17:26.376Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 0
File: :0-0
Timestamp: 2025-09-02T19:17:26.376Z
Learning: In Bun's Zig codebase, when handling error unions where the same cleanup operation (like `rawFree`) needs to be performed regardless of success or failure, prefer using boolean folding with `else |err| switch (err)` over duplicating the cleanup call in multiple switch branches. This approach avoids code duplication while maintaining compile-time error checking.

Applied to files:

  • src/bun.js/node/node_fs.zig
📚 Learning: 2025-10-08T13:56:00.875Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 23373
File: src/bun.js/api/BunObject.zig:2514-2521
Timestamp: 2025-10-08T13:56:00.875Z
Learning: For Bun codebase: prefer using `bun.path` utilities (e.g., `bun.path.joinAbsStringBuf`, `bun.path.join`) over `std.fs.path` functions for path operations.

Applied to files:

  • src/bun.js/node/node_fs.zig
📚 Learning: 2026-01-05T16:32:07.551Z
Learnt from: alii
Repo: oven-sh/bun PR: 25474
File: src/bun.js/event_loop/Sigusr1Handler.zig:0-0
Timestamp: 2026-01-05T16:32:07.551Z
Learning: In Zig codebases (e.g., Bun), treat std.posix.sigaction as returning void and do not perform runtime error handling for its failure. The Zig standard library views sigaction failures as programmer errors (unreachable) because they only occur with invalid signals like SIGKILL/SIGSTOP. Apply this pattern across Zig files that call sigaction (e.g., crash_handler.zig, main.zig, filter_run.zig, process.zig) and ensure failures are not handled as recoverable errors; prefer reaching an explicit unreachable/compile-time assumption when such failures are detected.

Applied to files:

  • src/bun.js/node/node_fs.zig
  • src/sys.zig
  • src/glob/GlobWalker.zig
📚 Learning: 2025-10-19T23:27:43.240Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 23840
File: src/bun.js/node/node_fs_stat_watcher.zig:400-411
Timestamp: 2025-10-19T23:27:43.240Z
Learning: In Zig code (e.g., src/bun.js/node/node_fs_stat_watcher.zig), taking the address of a function return value (e.g., &this.getLastStat()) is valid when passed as a const pointer parameter (e.g., *const bun.sys.PosixStat) to another function in the same expression. The compiler materializes the temporary with appropriate lifetime for the call scope.

Applied to files:

  • src/sys.zig
🔇 Additional comments (5)
src/sys.zig (1)

747-762: Confirm whether Windows lstatat should preserve symlink-following semantics.

On Windows, lstatat delegates to fstatat, which opens the file with flags=0 (no O.NOFOLLOW). This means Windows will follow symlinks by default, while the POSIX implementation correctly uses std.posix.AT.SYMLINK_NOFOLLOW to prevent following symlinks. The function's documentation states it does not follow symlinks, and all usages in GlobWalker.zig and node_fs.zig explicitly rely on this behavior.

Since this PR targets Linux FUSE/bind-mount issues where this function is primarily used, the Windows inconsistency may be acceptable. However, if Windows support is required, consider either passing O.NOFOLLOW to the file open call or using an alternative stat approach (similar to how lstat delegates to sys_uv.lstat on Windows).

src/bun.js/node/node_fs.zig (2)

4630-4687: DT_UNKNOWN lazy lstatat fallback in async recursive readdir looks correct and consistent

The new effective_kind tracking plus .unknown branch that calls bun.sys.lstatat(fd, current.name.sliceAssumeZ()) is wired correctly:

  • You reuse the existing path-length guard before enqueueing, so recursion limits remain unchanged.
  • lstatat (no-follow) preserves symlink identity while still letting you distinguish directories/symlinks from regular files.
  • On stat success, you update effective_kind and only enqueue when the real kind is .directory or .sym_link, which is exactly what the existing recursion logic expects.
  • On stat error, you skip recursion and leave effective_kind as .unknown, so emitted Dirent.kind correctly reflects that the type could not be resolved.
  • Using effective_kind in the Dirent construction ensures callers of recursive readdir see resolved kinds instead of .unknown where possible, without touching the non-recursive path.

Overall, this aligns with the PR’s DT_UNKNOWN strategy and should restore correct behavior on bind/FUSE/NFS without regressions on normal filesystems.


4798-4850: Sync recursive readdir DT_UNKNOWN handling mirrors async path appropriately

The synchronous readdirWithEntriesRecursiveSync changes mirror the async logic well:

  • effective_kind is initialized from current.kind and only overridden when lstatat succeeds, so type information is never uninitialized.
  • The .unknown branch applies the same MAX_PATH_BYTES guard and only pushes onto the basename stack when the resolved kind is .directory or .sym_link, reusing the existing recursion contract.
  • Updating Dirent.kind = effective_kind ensures recursive sync readdir exposes resolved kinds consistently with the async implementation.

The symmetry between async and sync paths here is good and reduces divergence in behavior across the two flavors.

src/glob/GlobWalker.zig (2)

157-161: LGTM: Clean non-following stat implementation.

The lstatat function correctly provides symlink-preserving stat semantics by delegating to Syscall.lstatat on POSIX and falling back to statatWindows on Windows, mirroring the pattern of the existing statat function.


924-924: The silent skip of stat failures on unknown entries is intentional and appropriate.

Line 924 silently continues when lstatat fails on DT_UNKNOWN entries, which is intentional per the comments at lines 911-913. This is a performance optimization (PR #18172 lazy stat pattern) specific to filesystems like Docker bind mounts, FUSE, and NFS that report DT_UNKNOWN for d_type. When the d_type is already unknown and the lazy stat fails, skipping the entry is reasonable—there's no additional information to determine. This approach is consistent throughout the codebase, including identical patterns in src/bun.js/node/node_fs.zig (lines 4659-4667 and 4820-4828), confirming it is the intended design for handling unknown directory entries.

Comment on lines 911 to 994
// Some filesystems (e.g., Docker bind mounts, FUSE, NFS) return
// DT_UNKNOWN for d_type. Use lazy stat to determine the real kind
// only when needed (PR #18172 pattern for performance).
.unknown => {
// First check if name might match pattern (avoid unnecessary stat)
const might_match = this.walker.matchPatternImpl(dir_iter_state.pattern, entry_name);
if (!might_match) continue;

// Need to stat to determine actual kind (lstatat to not follow symlinks)
const name_z = this.walker.arena.allocator().dupeZ(u8, entry_name) catch bun.outOfMemory();
const stat_result = Accessor.lstatat(dir.fd, name_z);
const real_kind: std.fs.File.Kind = switch (stat_result) {
.result => |st| bun.sys.kindFromMode(st.mode),
.err => continue, // Skip entries we can't stat
};

// Process based on actual kind
switch (real_kind) {
.file => {
const matches = this.walker.matchPatternFile(entry_name, dir_iter_state.component_idx, dir.is_last, dir_iter_state.pattern, dir_iter_state.next_pattern);
if (matches) {
const prepared = try this.walker.prepareMatchedPath(entry_name, dir.dir_path) orelse continue;
return .{ .result = prepared };
}
},
.directory => {
var add_dir: bool = false;
const recursion_idx_bump_ = this.walker.matchPatternDir(dir_iter_state.pattern, dir_iter_state.next_pattern, entry_name, dir_iter_state.component_idx, dir_iter_state.is_last, &add_dir);

if (recursion_idx_bump_) |recursion_idx_bump| {
const subdir_parts: []const []const u8 = &[_][]const u8{
dir.dir_path[0..dir.dir_path.len],
entry_name,
};

const subdir_entry_name = try this.walker.join(subdir_parts);

if (recursion_idx_bump == 2) {
try this.walker.workbuf.append(
this.walker.arena.allocator(),
WorkItem.new(subdir_entry_name, dir_iter_state.component_idx + recursion_idx_bump, .directory),
);
try this.walker.workbuf.append(
this.walker.arena.allocator(),
WorkItem.new(subdir_entry_name, dir_iter_state.component_idx, .directory),
);
} else {
try this.walker.workbuf.append(
this.walker.arena.allocator(),
WorkItem.new(subdir_entry_name, dir_iter_state.component_idx + recursion_idx_bump, .directory),
);
}
}

if (add_dir and !this.walker.only_files) {
const prepared_path = try this.walker.prepareMatchedPath(entry_name, dir.dir_path) orelse continue;
return .{ .result = prepared_path };
}
},
.sym_link => {
if (this.walker.follow_symlinks) {
const subdir_parts: []const []const u8 = &[_][]const u8{
dir.dir_path[0..dir.dir_path.len],
entry_name,
};
const entry_start: u32 = @intCast(if (dir.dir_path.len == 0) 0 else dir.dir_path.len + 1);
const subdir_entry_name = try this.walker.join(subdir_parts);

try this.walker.workbuf.append(
this.walker.arena.allocator(),
WorkItem.newSymlink(subdir_entry_name, dir_iter_state.component_idx, entry_start),
);
} else if (!this.walker.only_files) {
const matches = this.walker.matchPatternFile(entry_name, dir_iter_state.component_idx, dir_iter_state.is_last, dir_iter_state.pattern, dir_iter_state.next_pattern);
if (matches) {
const prepared_path = try this.walker.prepareMatchedPath(entry_name, dir.dir_path) orelse continue;
return .{ .result = prepared_path };
}
}
},
else => {}, // Skip other types (block devices, etc.)
}
continue;
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Significant code duplication across file/directory/symlink handling.

The logic for handling .file (lines 929-935), .directory (lines 936-968), and .sym_link (lines 970-989) within the .unknown case duplicates the existing handlers for .file (lines 832-838), .directory (lines 840-874), and .sym_link (lines 876-909). This creates ~60 lines of duplicated logic that must be maintained in parallel.

♻️ Refactoring approach to eliminate duplication

Consider extracting the kind-specific processing logic into helper methods that can be called from both the direct-kind cases and the .unknown fallback case:

// Helper for file processing
fn processFileEntry(
    this: *Iterator,
    entry_name: []const u8,
    dir_iter_state: *const IterState.Directory,
) !Maybe(?MatchedPath) {
    const matches = this.walker.matchPatternFile(
        entry_name,
        dir_iter_state.component_idx,
        dir_iter_state.is_last,
        dir_iter_state.pattern,
        dir_iter_state.next_pattern
    );
    if (matches) {
        const prepared = try this.walker.prepareMatchedPath(
            entry_name,
            dir_iter_state.dir_path
        ) orelse return .{ .result = null };
        return .{ .result = prepared };
    }
    return .{ .result = null };
}

Then the .unknown case becomes:

.unknown => {
    const might_match = this.walker.matchPatternImpl(dir_iter_state.pattern, entry_name);
    if (!might_match) continue;

    const name_z = this.walker.arena.allocator().dupeZ(u8, entry_name) catch bun.outOfMemory();
    const real_kind: std.fs.File.Kind = switch (Accessor.lstatat(dir.fd, name_z)) {
        .result => |st| bun.sys.kindFromMode(st.mode),
        .err => continue,
    };

    switch (real_kind) {
        .file => {
            if (try this.processFileEntry(entry_name, dir_iter_state)) |result| {
                return result;
            }
        },
        .directory => {
            if (try this.processDirectoryEntry(entry_name, dir_iter_state, dir.dir_path)) |result| {
                return result;
            }
        },
        .sym_link => {
            if (try this.processSymlinkEntry(entry_name, dir_iter_state, dir.dir_path)) |result| {
                return result;
            }
        },
        else => {},
    }
    continue;
},

This approach ensures that changes to file/directory/symlink processing logic only need to be made once.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e665293 and a27b282.

📒 Files selected for processing (1)
  • src/sys.zig
🧰 Additional context used
📓 Path-based instructions (2)
**/*.zig

📄 CodeRabbit inference engine (CLAUDE.md)

In Zig code, be careful with allocators and use defer for cleanup

Files:

  • src/sys.zig
src/**/*.zig

📄 CodeRabbit inference engine (src/CLAUDE.md)

src/**/*.zig: Use the # prefix for private fields in Zig structs, e.g., struct { #foo: u32 };
Use Decl literals in Zig, e.g., const decl: Decl = .{ .binding = 0, .value = 0 };
Place @import statements at the bottom of the file in Zig (auto formatter will handle positioning)
Never use @import() inline inside functions in Zig; always place imports at the bottom of the file or containing struct

Files:

  • src/sys.zig
🧠 Learnings (1)
📚 Learning: 2026-01-05T16:32:07.551Z
Learnt from: alii
Repo: oven-sh/bun PR: 25474
File: src/bun.js/event_loop/Sigusr1Handler.zig:0-0
Timestamp: 2026-01-05T16:32:07.551Z
Learning: In Zig codebases (e.g., Bun), treat std.posix.sigaction as returning void and do not perform runtime error handling for its failure. The Zig standard library views sigaction failures as programmer errors (unreachable) because they only occur with invalid signals like SIGKILL/SIGSTOP. Apply this pattern across Zig files that call sigaction (e.g., crash_handler.zig, main.zig, filter_run.zig, process.zig) and ensure failures are not handled as recoverable errors; prefer reaching an explicit unreachable/compile-time assumption when such failures are detected.

Applied to files:

  • src/sys.zig

Use O.NOFOLLOW flag which maps to FILE_OPEN_REPARSE_POINT on Windows,
making lstatat behave like POSIX lstat (doesn't follow symlinks).

This addresses CodeRabbit feedback about Windows symlink handling parity.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants