Conversation
|
Warning Review limit reached
Next review available in: 9 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (4)
Comment |
|
Status: the diff is green, CI is red on infrastructure, this needs a maintainer. ReproductionThe snippet in the description, against Fail-before / pass-after on both test files with without
|
| build | sha | outcome |
|---|---|---|
| 68833 | 25ff333a |
cookie-map.test.ts failed on stale Expires assertions, fixed on main by #33425, which landed one commit after this branch's base. Rebasing cleared it. |
| 68875 | bf973cbf |
281 jobs passed. One failure: darwin 26 aarch64 - test-bun shard 1 hit buildkite-agent artifact download timed out after 120s. Zero tests ran on it; the darwin aarch64 - build-bun step passed and shard 0 downloaded the same artifact and passed. |
| 68970 | 66829226 |
Zero jobs failed, zero annotations, zero tests run. Build steps expired waiting for agents on every platform (linux aarch64 - build-cpp, darwin aarch64 - build-rust, windows x64 - build-rust, freebsd x64 - build-bun, ...), leaving 200 downstream jobs waiting_failed and 50 expired. |
66829226 is an empty ci: retrigger commit, so its tree is identical to bf973cbf. The real test evidence is 68833 and 68875.
Both new tests did run on real Windows, in 68833, in separate shards of the 8-way :windows: 2019 x64 - test-bun lane:
| file | shard | result |
|---|---|---|
test/js/bun/plugin/plugins.test.ts |
0/8 | 35 pass, 0 fail |
test/js/bun/plugin/plugin-namespace-drive-letter.test.ts |
7/8 | passed |
So the per-platform assertions hold on Windows hardware: q:/pp threw there as a drive root, and C:\... was not captured by the registered C namespace.
I'm not pushing another ci: retrigger; a re-roll already went into 68970 and produced no signal. A maintainer re-running the build, or a fresh push, should clear it.
Open question
One user-visible semantic changes off Windows. It's written up in the description under "Behavior change worth a maintainer's eye", with the evidence that stock 1.4.0 already reads C:\... as the C namespace through onResolve, so this aligns the last disagreeing site rather than inventing a reading. Happy to drop the platform gate if you'd rather keep the old non-Windows behavior; it just leaves single-letter namespaces with absolute paths broken.
There was a problem hiding this comment.
I didn't find any issues — the fix looks correct and the two sides now agree on the drive-root rule — but this touches moduleLoaderResolve and changes observable behavior on non-Windows (a single-letter namespace like C now captures C:\\... imports where it previously didn't), so a maintainer should confirm that's the intended semantics.
Extended reasoning...
Overview
This PR fixes runtime Bun.plugin so that single-letter namespace values returned from onResolve reach their onLoad handler. Two sites are touched:
src/jsc/bindings/ZigGlobalObject.cpp—moduleLoaderResolve's plugin-namespace short-circuit previously excluded anyX:prefix on every platform; it now only excludes actual drive roots (X:\/X:/) and only under#if OS(WINDOWS), via a newisWindowsDriveRoothelper.src/bundler/transpiler.rs—PluginRunner::extract_namespacehad an exclusive-bounds bug (> b'a' && < b'z', missing a/z/A/Z) and alen > 3check that skipped the bare 3-byte root; both replaced with the canonicalis_drive_letter/is_sep_anyhelpers andlen > 2.
Two tests are updated/added: a new subprocess test in plugins.test.ts covering one- and two-letter namespaces across bare/nested/rooted paths, and plugin-namespace-drive-letter.test.ts re-tightened to assert per-platform behavior.
Security risks
None identified. This is namespace/path prefix parsing for plugin dispatch; no auth, crypto, filesystem writes, or untrusted-length arithmetic is involved. The change narrows a heuristic rather than loosening one.
Level of scrutiny
Moderate-to-high. moduleLoaderResolve sits on the critical module-import path for the entire runtime, and the change is intentionally a behavior change:
- On non-Windows, a registered single-letter onLoad namespace (e.g.
C) will now capture a specifier likeC:\\foo\\bar.js, whereas before it fell through to the filesystem resolver and failed. The updatedplugin-namespace-drive-letter.test.tsinverts its non-Windows expectation accordingly. - On Windows,
extract_namespacenow treatsa:\,z:\,A:\,Z:\, and the bare 3-byteC:\as drives where it previously (buggily) treated them as namespaces.
Both changes look correct and bring the C++ and Rust sides into agreement, but they do alter user-observable resolution results, which is the kind of thing a maintainer should sign off on.
Other factors
The reasoning in the PR description is thorough, the diff is small and mechanical, cross-references between the two implementations are added in comments, and test coverage is good (per-platform assertions, multiple path shapes). No bugs were found by the automated bug hunt. CI is still building at the time of review.
… drive
moduleLoaderResolve returns a module key unchanged when its "ns:" prefix
names a registered onLoad namespace, so the C++ loader does not re-resolve a
key that onResolve already produced. Its drive-letter carve-out matched any
"<letter>:" prefix on every platform, so a key like "q:pp" fell through to
the filesystem resolver and the import failed with "Cannot find package
'q:pp'". Restrict it to an actual Windows drive root ("C:\\" / "C:/") on
Windows, matching PluginRunner::extract_namespace, which decides the namespace
everywhere past that point.
extract_namespace had the same carve-out but excluded the a/z/A/Z drive
letters through an exclusive comparison; use is_drive_letter so both sides
state the same rule.
25ff333 to
bf973cb
Compare
Worth being precise here, because the runtime already had these semantics everywhere except the one line this PR touches. On stock 1.4.0, no patch, Linux, a plugin registered for namespace Bun.plugin({
name: "probe",
setup(b) {
b.onResolve({ filter: /.*/, namespace: "C" }, args => {
console.log("onResolve(ns=C) path=" + JSON.stringify(args.path));
return { path: args.path, namespace: "C" };
});
b.onLoad({ filter: /.*/, namespace: "C" }, () => ({ contents: `export default "captured"`, loader: "js" }));
},
});
console.log("result:", (await import("C:\\__definitely_missing__\\x.js")).default);That is The platform gate is also load-bearing rather than cosmetic. Keeping the carve-out on all platforms and only requiring the separator would still drop The Windows expectations are not theoretical: Unrelated CI note: the red Windows lanes on the previous push were |
There was a problem hiding this comment.
I didn't find any bugs, but this touches module-resolution logic in moduleLoaderResolve with platform-divergent behavior (and a subtle non-Windows change where C:\\... now reaches a registered C namespace instead of being rejected), so it's worth a human sanity-check before merging.
Extended reasoning...
Overview
This PR fixes runtime Bun.plugin handling of single-letter namespaces. Two code changes:
src/jsc/bindings/ZigGlobalObject.cpp: themoduleLoaderResolveshort-circuit previously excluded any key matchingcolon == 1 && isASCIIAlpha(key[0])on all platforms, which meant every single-letter plugin namespace (q:pp) was misread as a Windows drive and fell through to the filesystem resolver. It now only carves out actual drive roots (C:\/C:/) and only under#if OS(WINDOWS).src/bundler/transpiler.rs:extract_namespace's Windows-only drive check used exclusive comparisons (> b'a' && < b'z'), missinga/z/A/Z; it now delegates tois_drive_letter. The length check drops from> 3to> 2so a bareC:\counts as a drive root.
Two test files updated: a new comprehensive test in plugins.test.ts covering one/two-letter namespaces across bare/nested/rooted paths with per-platform assertions, and plugin-namespace-drive-letter.test.ts reworked to assert both halves of the rule per platform.
Security risks
None identified. This is namespace-prefix parsing for plugin routing; no auth, crypto, or untrusted-input allocation is involved.
Level of scrutiny
Moderate-to-high. moduleLoaderResolve sits on the hot path for every ES module import when any runtime plugin registers an onLoad namespace, and the fix intentionally changes behavior on non-Windows: a static import like C:\\foo with a registered single-letter C namespace now reaches the plugin's onLoad instead of being rejected by the filesystem resolver (the old test asserted rejected: on all platforms; the new test asserts loaded:from-plugin on non-Windows). That aligns the C++ side with what extract_namespace already did on the Rust side, so it's the more consistent behavior — but it is a user-visible semantic change worth a maintainer's eye.
Other factors
- The Rust change is Windows-only via
cfg!(windows); the> 3→> 2anda/zboundary fix only affect Windows, and the PR notes the Windows branch was exercised by forcing the predicate on Linux rather than on a Windows machine. - Tests are well-constructed (subprocess isolation, per-platform assertions, fail-before/pass-after verified per the robobun comment) and the bug hunter found nothing.
- The change is small and the reasoning in the description is thorough, but module resolution + cross-platform divergence puts this outside the "mechanical/obvious" bucket for auto-approval.
That caveat is now retired, and I've updated the description accordingly. Both new test files did run on real Windows in the previous CI build, in separate shards of
So the per-platform assertions held on Windows hardware:
Answered in #33443 (comment), with a repro showing that stock 1.4.0 on Linux already routes Still worth a maintainer's eye on that semantic, which is why I'm flagging rather than assuming. |
darwin-26-aarch64 test shard 1 failed on a 120s buildkite artifact download timeout; no tests ran. The build step and sibling shard passed.
There was a problem hiding this comment.
I didn't find any issues — the fix is small and well-reasoned, and the two namespace-parsing sites now agree — but it changes module-resolution behavior in moduleLoaderResolve and flips what C:\\... means on non-Windows when a single-letter namespace is registered, so a human should sanity-check the cross-platform semantics.
Extended reasoning...
Overview
This PR fixes runtime Bun.plugin losing single-letter namespaces. It touches two native sites plus tests:
src/jsc/bindings/ZigGlobalObject.cpp: extracts the drive-letter carve-out inmoduleLoaderResolveintoisWindowsDriveRoot(), which now only matches on Windows and only when the character after the colon is a path separator. Previouslycolon == 1 && isASCIIAlpha(key[0])matched every single-letter prefix on every platform.src/bundler/transpiler.rs:PluginRunner::extract_namespacereplaces the hand-rolled exclusive range check (> 'a' && < 'z', which excludeda/z/A/Z) withis_drive_letter(), and relaxeslen() > 3tolen() > 2so a bareC:\\counts as a drive root.- Two test files: a new
it.concurrentinplugins.test.tscovering one- and two-letter namespaces across bare/nested/rooted paths with per-platform assertions, andplugin-namespace-drive-letter.test.tsupdated to assert that non-Windows now treatsC:\\...as theCnamespace (a behavioral change this PR introduces).
Security risks
None identified. This is specifier-classification logic; the change narrows a heuristic rather than widening trust. Bounds are respected (key.length() > 2 guards key[2]).
Level of scrutiny
Moderate-to-high. moduleLoaderResolve in ZigGlobalObject.cpp is on the hot path for every ES module import, and the change is intentionally cross-platform-divergent. Two behavioral shifts deserve a human eye:
- Non-Windows: a specifier like
C:\\foowith a single-letterCnamespace registered now short-circuits to the plugin instead of falling through to the filesystem resolver. The existing drive-letter test's assertion was inverted for non-Windows to reflect this. It's consistent with whatextract_namespacealready did downstream, but it is a user-visible change. - Windows:
a:\\x,z:\\x,A:\\x,Z:\\x, and bareC:\\are now classified as drive roots inextract_namespace(previously namespaces due to the exclusive-range and> 3bugs).
Other factors
The two implementations (isWindowsDriveRoot in C++ and extract_namespace in Rust) now agree, and each cross-references the other in comments. Test coverage is thorough with per-platform branching, and the PR description reports both files passing on the Windows CI lane. I verified is_drive_letter uses inclusive bounds. No prior reviews or comments on the PR. Deferring because module resolution is critical enough that the cross-platform semantics change should be confirmed by a maintainer.
|
Same flag third time, so I've stopped burying the answer in comments and moved it into the description under "Behavior change worth a maintainer's eye". The old description only mentioned the Windows-side change, which made it look like nothing moved off Windows. That was the real omission, and it's fixed. The one-line version, for anyone skimming: off Windows this does change what Happy to drop the gate if a maintainer prefers the old non-Windows behavior; it just leaves absolute paths with one-letter namespaces broken. |
|
Closing as part of a cleanup of stale pull requests. This PR has had no new commits since 2026-07-06, it conflicts with main, and its last CI run failed. This is not a judgment on the fix itself. If the problem still reproduces on a current build, reopen this PR after a rebase or open a new one against main. |
A runtime
Bun.pluginonResolveresult with a single-letternamespacesilently loses the namespace: the registeredonLoadnever runs and the import fails. Two or more letters work, and the same plugin works underBun.build.Cause
onResolve's{ path, namespace }is serialized into the module key"ns:path", and the loader re-parses it. Since #29393 the C++ module loader callsresolve()a second time on keys thatmoduleLoaderImportModulealready resolved, somoduleLoaderResolveshort-circuits a key whose"ns:"prefix names a registeredonLoadnamespace and returns it unchanged.That short-circuit carved out Windows drive letters with
colon == 1 && isASCIIAlpha(key[0]), which is true for every single-letter namespace, on every platform."q:pp"was read as driveq:, fell through to the filesystem resolver, and failed.PluginRunner::extract_namespace, which decides the namespace everywhere past that point (Bun__runVirtualModule,Zig__GlobalObject__resolve), already had the correct rule, so the two disagreed about what"q:pp"means.Fix
moduleLoaderResolvenow only treats a prefix as a drive when it is an actual Windows drive root (C:\orC:/) and the process is running on Windows, matchingextract_namespace.extract_namespacespelled the same carve-out with an exclusive comparison (specifier[0] > b'a' && specifier[0] < b'z'), which excluded thea,z,AandZdrive letters; it now usesbun_paths::resolve_path::is_drive_letterso both sides state the same rule.No change on non-Windows for
extract_namespace; on Windows,a:\x/z:\x/A:\x/Z:\xand the bare drive rootC:\are now drives rather than namespaces.Behavior change worth a maintainer's eye
One user-visible semantic changes off Windows, and it is the only judgement call in this PR.
With a single-letter namespace registered for
onLoadand noonResolvefor it, a specifier shaped likeC:\foo\x.jsnow reaches that plugin instead of the filesystem resolver. This is not a new reading ofC:\..., it is the reading the rest of the runtime already used. On stock 1.4.0, unpatched, Linux, anonResolveregistered for namespaceCalready claims it:extract_namespacegates its drive-root carve-out on Windows, so off WindowsC:\foohas always been the namespaceCtoZig__GlobalObject__resolveandBun__runVirtualModule.moduleLoaderResolvewas the single site that disagreed. This PR makes it agree.The platform gate is load-bearing rather than cosmetic. Keeping the carve-out on every platform and only requiring the separator would still drop
{ path: "/pp", namespace: "q" }, whose key isq:/pp. That case and theC:\...case are the same shape pulling in opposite directions, so one of them has to lose per platform: drive roots exist only on Windows, so the drive reading wins there and the namespace reading wins everywhere else.If you would rather keep
C:\...rejecting off Windows, say so and I will drop the platform gate, at the cost of leaving single-letter namespaces with absolute paths broken.Verification
test/js/bun/plugin/plugins.test.tsgainsonResolve namespaces of any length reach onLoad, covering one-letter (q,A) and two-letter namespaces across bare (q:pp), nested (q:sub/pp) and rooted (q:/pp) paths.q:/ppis the one shape a one-letter namespace cannot have on Windows (it is a drive root there), so that case is asserted per platform.test/js/bun/plugin/plugin-namespace-drive-letter.test.tskeeps asserting that a one-letter namespace never capturesC:\...on Windows, and now asserts the other half of the rule elsewhere: platforms without drive roots read the prefix as the namespace, likeextract_namespacealready did foronResolve.Both fail on
mainand pass with the fix.The Windows half of the rule is covered on real Windows, not just by inspection: on the previous CI run both files ran on the
windows 2019 x64lane and passed (plugins.test.tsin shard 0/8,35 pass, 0 fail;plugin-namespace-drive-letter.test.tsin shard 7/8). Before pushing, the#if OS(WINDOWS)branch was also compiled and exercised on Linux by forcing the predicate on, which flipped both per-platform assertions to their Windows values as expected.The red check is infrastructure, not this diff: the latest build expired waiting for agents on every platform and ran zero tests, and the one before it failed a single darwin shard on a 120s artifact-download timeout. Breakdown in the first comment.