Repository navigation
Description
What version of Bun is running?
1.3.14+0d9b296af
What platform is your computer?
Darwin 26.5.1 arm64 (macOS, Apple Silicon)
What steps will reproduce the bug?
Two files, no dependencies.
main.ts:
Bun.plugin({
name: "repro",
setup(build) {
build.onResolve({ filter: /plain\.ts/ }, (args) => {
console.log("[plugin] onResolve fired:", args.path)
return undefined
})
build.onLoad({ filter: /plain\.ts/ }, (args) => {
console.log("[plugin] onLoad fired:", args.path)
return { contents: "export const source = 'plugin'", loader: "ts" }
})
},
})
const mod = await import(process.argv[2]).catch((err) => ({ source: "error: " + err.message }))
console.log("source =", mod.source)plain.ts:
export const source = "disk"Run:
bun main.ts "$PWD/plain.ts?v=123"
bun main.ts "$PWD/plain.ts?v=123.456"Also reproduces identically in a compiled binary:
bun build --compile --outfile repro main.ts
./repro "$PWD/plain.ts?v=123"
./repro "$PWD/plain.ts?v=123.456"What is the expected behavior?
Both imports go through the runtime plugin pipeline. Both filters match the specifier in both cases, so the output should be:
[plugin] onResolve fired: /.../plain.ts?v=123.456
[plugin] onLoad fired: /.../plain.ts?v=123.456
source = plugin
What do you see instead?
With ?v=123 the plugin works:
[plugin] onResolve fired: /.../plain.ts?v=123
[plugin] onResolve fired: /.../plain.ts?v=123
[plugin] onLoad fired: /.../plain.ts?v=123
[plugin] onResolve fired: /.../plain.ts?v=123
source = plugin
With ?v=123.456 the plugin pipeline is bypassed entirely — neither onResolve nor onLoad is invoked, and the module is loaded from disk by Bun's native loader:
source = disk
The trigger is not "any dot in the query". Empirically, the plugin pipeline is skipped exactly when the text after the last . in the full specifier starts with a non-letter (digit, another dot, or end of string). Matrix of query strings appended to plain.ts (same results under plain bun and in a bun build --compile binary, Bun 1.3.14):
| query | plugin hooks fire? |
|---|---|
| (none) | yes |
?v=123 |
yes |
?v.x=1 |
yes |
?v=1.x |
yes |
?v=1.x4 |
yes |
?a.b |
yes |
?v=1#a.b |
yes |
?v=123.456 |
no |
?v=.456 |
no |
?v=x.4 |
no |
?v=1.4a |
no |
?v=123. |
no |
?v=1.. |
no |
?v=1.2.3 |
no |
?v=1.x.4 |
no |
?123.456 |
no |
?mtime=1786494961337.0317 |
no |
This pattern suggests the loader/extension is being detected from the last . of the raw specifier including the query string, and when that pseudo-extension doesn't look like one (starts with a digit/dot), the module takes a code path that never consults runtime plugins, silently falling back to the native loader instead.
The failure is silent: the import still succeeds with the untransformed file contents, so any transform the plugin was supposed to apply is just lost. If the plugin was supplying JSX config or virtual modules, the symptom surfaces later as e.g. ResolveMessage: Cannot find module '...' from the natively-transpiled output.
Additional information
- Reproduces both under plain
bun main.tsand inbun build --compilebinaries. In compiled binaries it tends to surface as a hard failure because the native transpiler's output references packages that aren't resolvable from inside the binary (that's how we found it). - Real-world impact: OpenCode's TUI cache-busts dynamic plugin imports with
?mtime=${stat.mtimeMs}.stat.mtimeMsis frequently fractional (e.g.1786494961337.0317), which silently disabled the runtime plugin that provides the JSX transform for external.tsxplugin files, producingCannot find module '@opentui/solid/jsx-dev-runtime'in the compiled binary. Rounding the mtime works around it. - Not a filter-regex problem: the
onLoad/onResolvefilters above match the queried specifier, and a catch-allfilter: /.*/behaves the same.build.module(...)registrations still work in the failing cases; only the file's own resolve/load interception is skipped. - Bun v1.3.14 (
1.3.14+0d9b296af), macOS 26.5.1, arm64.
Activity
robobun commentedon Aug 12, 2026
Reproduced and fixed in #37702: the plugin pre-filter was reading the file extension from the last
.of the specifier including the query string. The PR is ready; remaining CI red is a pre-existing unrelated failure on one macOS lane.github-actions commentedon Aug 12, 2026
Found 1 possible duplicate issue:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
github-actions commentedon Aug 15, 2026
Duplicate of #4609.
This issue has been automatically closed as a duplicate.
If this is incorrect, please re-open this issue or create a new one.
🤖 Generated with Claude Code
Test the rest of the query rows of #37699 and its compiled executable