Skip to content

Runtime Bun.plugin onResolve/onLoad silently bypassed when import query string contains a dot followed by a non-letter (e.g. ?mtime=123.456) #37699

Closed as duplicate

Description

@kitlangton

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.ts and in bun build --compile binaries. 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.mtimeMs is frequently fractional (e.g. 1786494961337.0317), which silently disabled the runtime plugin that provides the JSX transform for external .tsx plugin files, producing Cannot find module '@opentui/solid/jsx-dev-runtime' in the compiled binary. Rounding the mtime works around it.
  • Not a filter-regex problem: the onLoad/onResolve filters above match the queried specifier, and a catch-all filter: /.*/ 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

  1. robobun commented on Aug 12, 2026

    @robobun
    Collaborator

    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.

  2. github-actions commented on Aug 12, 2026

    @github-actions
    Contributor

    Found 1 possible duplicate issue:

    1. Bun plugin can't load files with number in the file extension #4609

    This issue will be automatically closed as a duplicate in 3 days.

    • If your issue is a duplicate, please close it and 👍 the existing issue instead
    • To prevent auto-closure, add a comment or 👎 this comment

    🤖 Generated with Claude Code

  3. github-actions commented on Aug 15, 2026

    @github-actions
    Contributor

    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

  4. added a commit that references this issue on Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions