Repository navigation
Conversation
The children loop in the sync walker and in the async walker returned from the whole method when a child path was already in the seen cache. Every later sibling was dropped: - ['src/*.ts', './src/helpers/*.ts'] did not return 'src/index.ts'. Bun 1.3 returned it. - 'a/**/../*' did not return 'a', 'a/c', 'a/x' or 'a/z'. Remove the check at both sites, as Node.js did in v26.8.0 and v24.21.0: nodejs/node@0ea2c86 Update test/js/node/test/parallel/test-fs-glob.mjs to the v26.8.2 copy, which adds the upstream test for this bug.
|
Warning Review limit reached
On-demand reviews are free for the next 5 days. After that, they cost $0.25 per reviewed file. Or wait 3 minutes for your next included review. View limit detailsLimit details: You’ve used all 10 included reviews currently available. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Essentials Run ID: 📒 Files selected for processing (3)
Comment |
|
Status for #42870. Reproduction, on bun 1.4.3-canary.1+09bb54630 and on const fs = require("node:fs"), os = require("node:os"), path = require("node:path");
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "glob-"));
fs.mkdirSync(path.join(cwd, "src/helpers"), { recursive: true });
for (const f of ["src/index.ts", "src/helpers/a.ts", "src/helpers/b.ts"]) fs.writeFileSync(path.join(cwd, f), "");
console.log(fs.globSync(["src/*.ts", "./src/helpers/*.ts"], { cwd }).sort());
// bun 1.4.3-canary, Node.js v26.3.0: [ "src/helpers/a.ts", "src/helpers/b.ts" ]
// this branch, Node.js v26.8.2: [ "src/helpers/a.ts", "src/helpers/b.ts", "src/index.ts" ]
|
There was a problem hiding this comment.
Beyond the inline findings, I also checked that the deletion at both walker sites (src/js/internal/fs/glob.ts:640 sync, :856 async) is exactly the upstream nodejs/node@ 0ea2c86b change with no drift between the two copies, and that the re-vendored glob - seen cache case in test/js/node/test/parallel/test-fs-glob.mjs still fails on the base branch even though Bun's port re-sorts dirents (glob.ts:118, :276), which overrides the child's pinned ['c','b','x','z'] readdir order — so it exercises a different traversal order than Node, but not a vacuous one.
Extended reasoning...
Two findings were already confirmed inline (result loss with merged brace/array patterns once the early return is gone, and the .. continuation #subpatterns claim now firing for already-seen children). This note only records what else was examined: the sync and async walkers were diffed against each other and against the cited upstream commit and match; the vendored Node test's readdir monkeypatch is partially neutralized by Bun's sortDirents (glob.ts:118/276), but the PR's own claim that it fails on main under the sorted order is consistent with the trace, so it was not treated as a bug. The pre-existing Cache.add suffix-merging gap is upstream behavior and out of scope for this change.
One verified lower-impact observation (a convention, logging or cleanup point) was not posted.
Findings marked 🟡 are optional suggestions and need no follow-up push.
Additional findings (outside the current diff — GitHub can't attach inline comments there):
-
🟡
src/js/internal/fs/glob.ts— Callers passing a pattern array or brace group that contains**/..can lose the second pattern's..continuation and get fewer results than on the base branch. With the early return gone, the..block at glob.ts:696-700 now runs for children the seen cache already holds, and it claims the#subpatternsslot forpathorparentwith a single-index child. A later pattern reaching the same block seeshas()true and silently skips its own continuation instead of appending. Fix: make the..block push onto the existing list the way#addSubpatterndoes at glob.ts:547-551 (both sync :696-700 and async :916-920), rather than skipping when the key is present.Extended reasoning...
On the base branch the early return at the old glob.ts:640 aborted the child loop for a seen child, so that pattern never reached the
..block and never set#subpatterns[path]. Now it does.#subpatterns.set(path, [pattern.child({nextIndex+1})])at :697 and :700 is guarded only by!this.#subpatterns.has(path). Unlike#addSubpattern(:547-551), which pushes when the key exists, this block discards the second pattern's continuation entirely. In a pattern array (or brace group, which is expanded to an array) where two patterns both contain**/..at different positions, or the same pattern is queued with two indexes, the first pattern to run the block wins the slot; the second pattern's{nextIndex+1}child forpath/parentis never queued. The dismissing finder only checked that#addSubpattern(entryPath, ...)cannot collide withpath/parent; it did not consider that the newly reachable..block itself is the writer that now fills the slot first. The PR author listsa/**/../{x,z}returning onlya/xas a symptom of exactly this skip, and calls it pre-existing,…Verification: nit (verified regression vs base, but only on a narrow input class: a pattern array / brace group with two or more
**/..patterns that have different targets plus a pattern that pre-marks a child directory with the same**/..suffix; upstream Node v26.8.2 has the same limitation after its identical fix). Triggering condition: a..continuation of one array pattern is claimed via… | normal…
|
Updated 10:15 PM PT - Sep 15th, 2026
✅ @robobun, your commit 59d6f4fc80eee648f6fad89581056186fe45f305 passed in 🧪 To try this PR locally: bunx bun-pr 42870That installs a local version of the PR into your bun-42870 --bun |
|
I ran both findings (Linux x64, results sorted).
The second repro is not a regression. How often the first case occurs, with each result compared to the run without the fix:
I tried the suggested change: Those are two deliberate divergences from Node.js, which returns the same results as this branch in v26.8.2 and in the nightly. So this PR stays the upstream change. #42876 has the repros, the three causes and the measurements, and the PR description now names this class of input. I can open the follow-up PR if a maintainer wants the divergence.
|
Problem
fs.globSync,fs.globandfs.promises.globdrop results.["src/*.ts", "./src/helpers/*.ts"]does not returnsrc/index.ts. Bun 1.3.14 returned it (regression in 1.4.0).a/**/../*does not returna,a/c,a/xora/z.src/js/internal/fs/glob.ts:640(sync walker) and:859(async walker). If the seen cache holds the child path, the loop runsreturnand skips every later sibling.lib/internal/fs/glob.jsfrom Node.js v26.3.0 (fs: port Node.js v26.3.0 fs tests and fix the gaps they surface — cp error semantics, watcher event delivery, watch ignore+AbortSignal, FileHandle pull/writer, glob port, opendir/Dir, mkdtempDisposable, rmdir-recursive end-of-life, mock.fn (+119 tests) #31830, Bun 1.4.0), which has this bug. Node.js fixed it in v26.8.0 and v24.21.0 (fs: fix glob early return skipping sibling entries nodejs/node#64895).Fix
cache.addstill stops a second traversal of the same path and pattern suffix, so the walk ends.test/js/node/fs/glob.test.ts(9 new tests) andtest/js/node/test/parallel/test-fs-glob.mjs(449 tests). The second file is now the v26.8.2 copy, with the upstream test for this bug. Withsrc/frommain, 9 and 1 of them fail. Results match Node.js v26.8.2.Background
*.tsshare a key../pattern first, so the cache holdssrc/helperswith*.ts. Thensrc/*.tslistssrc. The check fires on the childhelpers, and the walker never reachesindex.ts.Notes
Repro 1. Fixture:
src/index.ts,src/helpers/a.ts,src/helpers/b.ts.globSync(["src/*.ts", "./src/helpers/*.ts"], { cwd }):./src/helpers/a.ts,./src/helpers/b.ts,src/index.tssrc/helpers/a.ts,src/helpers/b.tssrc/helpers/a.ts,src/helpers/b.ts,src/index.tsThe brace form
{src/*.ts,./src/helpers/*.ts}behaves the same on 1.4.x. The reverse order of the array does not trigger the bug.Repro 2. Fixture:
a/b/c/d/,a/c/d/c/,a/x,a/z.globSync("a/**/../*", { cwd })returnsa/b,a/b/c,a/b/c/d,a/c/danda/c/d/conmainand on Node.js v26.3.0. This branch and Node.js v26.8.2 return those plusa,a/c,a/xanda/z. Bun 1.3.14 returned[]for this pattern.Real tree. Over Bun's
src/directory, the results of**/*.ts,js/**/../*,**/node/../*.rsand{js,runtime}/**/*.{ts,rs}are identical to Node.js v26.8.2.js/**/../*goes from 123 to 198 results.Upstream test. The re-vendored
test-fs-glob.mjsis byte-identical to Node.js v26.8.2. The new case (glob - seen cache) spawns a child with--expose-internalsand requiresinternal/fs/glob, which Bun supports. It fails onmainwith the upstream assertion (missing a/c from sync results) and passes here. The same file passes on Node.js v26.8.2 and fails on v26.3.0.Not changed here (tracked in #42876). The walker drops results in more cases. Node.js v26.8.2 and the v27 nightly of 2026-09-15 (native glob engine) return the same results as this branch, so this PR leaves them alone.
["x/**/y/*", "./x/y/**/y/*", "x/y/*"]overx/y/gandx/y/z/y/freturns onlyx/y/z/y/f.mainreturns all three paths, because the earlyreturnstops the walker beforeCache.addsees the merged pattern. This is the one known class of input where this branch returns less thanmain. It did not occur in 70,000 random pattern arrays. In 30,000 arrays built to share a tail, Node.js v26.8.2 lost a path against v26.3.0 in 4 and gained paths in 4,571. In 6,000 such arrays, this branch lost a path againstmainin 0 and gained paths in 936.["a/*/*", "a/**/c/*"]over the fixture oftest-fs-glob.mjsdoes not returna/c/d/c/b(the same onmain).Cache.addreports a repeat when any one index of the pattern is already in the cache, and it marks the other indexes although nothing walked them.a/**/../{x,z}overa/b/,a/x,a/zreturns onlya/x(the same onmain). Thenext === ".."branch queues a continuation with#subpatterns.setonly if no other pattern already queued that path.The readdir sort. The port sorts each readdir result, and its comment says that the bookkeeping is sensitive to entry order. This PR does not touch it, because the statement is still true. With every readdir result shuffled, 21 of 2661 random single patterns gave order-dependent results on Node.js v26.3.0 and 0 on v26.8.2. But on v26.8.2,
!(symlink)/?/**/../../!(symlink)over the fixture oftest-fs-glob.mjsreturnsafor only 44 of 80 orders.Other suites.
test-fs-glob-throw.mjs,test-path-glob.jsandtest/regression/issue/24007.test.tspass.no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/node/fs/glob.test.ts