Skip to content

Fix crash when onResolve plugin appends query string to file path#28626

Open
robobun wants to merge 6 commits intomainfrom
farm/7d18b4ae/fix-query-string-onresolve
Open

Fix crash when onResolve plugin appends query string to file path#28626
robobun wants to merge 6 commits intomainfrom
farm/7d18b4ae/fix-query-string-onresolve

Conversation

@robobun
Copy link
Copy Markdown
Collaborator

@robobun robobun commented Mar 28, 2026

Closes #28625

Problem

When a bundler plugin's build.onResolve hook returns a path with a query string (e.g., /path/to/file.html?env=browser) or hash fragment in the file namespace, the bundler tries to open the literal filename including the suffix, causing a "File not found" error or crash.

build.onResolve({filter: /\.html$/}, args => {
  return { path: args.path + '?env=browser' };
});

Cause

In BundleV2.onResolve, plugin-resolved paths are passed directly to Fs.Path.init without stripping query/fragment suffixes. The resolver already handles this for CSS imports (kind.isFromCSS()), but plugin-resolved paths in the file namespace bypass the resolver entirely and never get stripped.

Fix

Strip ? and # suffixes from plugin-resolved paths in the file namespace before constructing the Fs.Path, so the filesystem lookup uses the clean path.

Verification

  • USE_SYSTEM_BUN=1 bun test test/regression/issue/28625.test.ts → 2 fail
  • bun bd test test/regression/issue/28625.test.ts → 2 pass

Strip query string (?...) and fragment (#...) suffixes from
plugin-resolved paths in the file namespace before using them
for filesystem lookup. The bundler was trying to open the path
including the suffix as a literal filename, causing a 'File not
found' error or crash.
@robobun
Copy link
Copy Markdown
Collaborator Author

robobun commented Mar 28, 2026

Updated 2:04 PM PT - Mar 28th, 2026

@robobun, your commit bb91421 has 2 failures in Build #42585 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 28626

That installs a local version of the PR into your bun-28626 executable, so you can run:

bun-28626 --bun

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 28, 2026

Walkthrough

BundleV2 resolution now treats file-namespace results specially: if the resolved path's filename contains ? or #, it checks the filesystem and strips the suffix when the full path doesn't exist before creating Fs.Path. Adds regression tests that append ?version=1 and #section in build.onResolve results.

Changes

Cohort / File(s) Summary
Bundle Path Resolution
src/bundler/bundle_v2.zig
Handle file-namespace resolution specially: detect ?/# in the filename portion of result.path, use the full path if it exists on disk, otherwise strip the suffix from the first ?/# before initializing Fs.Path; set path.namespace = "file" only for file namespaces and preserve non-file namespaces unchanged.
Regression Test
test/regression/issue/28625.test.ts
Add two integration tests invoking Bun.build with a plugin build.onResolve that returns .txt paths appended with ?version=1 and #section, asserting builds succeed and no ASAN warnings appear.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main fix: preventing crashes when onResolve plugins append query strings to file paths.
Description check ✅ Passed The description covers all required sections: Problem, Cause, Fix, and Verification with specific details and commands.
Linked Issues check ✅ Passed The PR addresses issue #28625 by implementing the fix to strip query strings/fragments from plugin-resolved file namespace paths before filesystem lookup, matching the expected behavior.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the query string crash issue: core fix in BundleV2 and regression test for the specific issue.

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


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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 311b08bf-5f1e-49f5-8746-f312353de859

📥 Commits

Reviewing files that changed from the base of the PR and between 44f5b6a and 465da04.

📒 Files selected for processing (2)
  • src/bundler/bundle_v2.zig
  • test/regression/issue/28625.test.ts

Walkthrough

Modified the bundler to handle query strings and fragments in resolver paths by stripping them for filesystem lookups before setting the file namespace. Added regression tests verifying that onResolve hooks can append query strings or hash fragments to file paths.

Changes

Cohort / File(s) Summary
Bundler query/fragment handling
src/bundler/bundle_v2.zig
When resolver results are not external and use file namespace, extracts and strips query strings/fragments from the path using indexOfAny(result.path, "?#") for filesystem lookup, while setting namespace only for cleaned paths.
Regression test for issue #28625
test/regression/issue/28625.test.ts
Adds integration tests verifying onResolve plugin hooks can append query strings (?version=1) or hash fragments (#section) to file paths during build operations.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: fixing a crash when onResolve plugin appends query string to file path.
Description check ✅ Passed The PR description includes both required sections: 'What does this PR do?' (via the Problem/Cause/Fix sections) and verification steps, though not in the exact template format.
Linked Issues check ✅ Passed The PR successfully addresses issue #28625 by stripping query and hash suffixes from plugin-resolved file-namespace paths before filesystem lookup, matching the requirement to allow query strings without crashes.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the query string handling issue: bundler logic modification and targeted regression tests.

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


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

robobun and others added 2 commits March 28, 2026 19:35
- Check file existence before stripping ?/# suffixes, since these
  are valid filename characters on Unix (e.g. C#/main.js)
- Use test.concurrent for independent subprocess tests
- Add stderr assertions with ASAN warning filtering
Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/bundler/bundle_v2.zig`:
- Around line 2641-2650: The code uses bun.strings.indexOfAny(result.path, "?#")
which finds the first `?` or `#` anywhere and truncates too much; change the
logic that computes path_to_use (involving is_file_namespace and result.path) to
locate the last occurrence of `?` or `#` (scan from the right or use a
lastIndexOfAny helper) and only strip the trailing query/fragment suffix if the
full path doesn't exist (use bun.sys.exists(result.path) to decide). Update the
branch that constructs path_to_use so it uses the right-most marker index when
slicing result.path instead of the first index returned by indexOfAny.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c10198f4-7ffc-496a-83cc-360127810208

📥 Commits

Reviewing files that changed from the base of the PR and between 465da04 and 18b0a9e.

📒 Files selected for processing (2)
  • src/bundler/bundle_v2.zig
  • test/regression/issue/28625.test.ts

indexOfAny finds the first ?/# anywhere in the path, which would
truncate at directory names like C#/. lastIndexOfAny finds the
last occurrence, which is always the plugin-appended suffix.
Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/bundler/bundle_v2.zig`:
- Around line 2658-2659: The code currently replaces the module key by calling
Fs.Path.init(path_to_use) and using that sanitized path for result.path, which
causes getOrPutPath and onLoad to see the stripped path and collapse distinct
resources; instead, keep result.path as the original plugin-visible module path
(do not overwrite result.path with Fs.Path.init), and only create a separate
cleaned disk-access path variable (e.g., cleaned_path =
Fs.Path.init(path_to_use) or similar) to use exclusively for file system
operations and deduping; update calls that need the filesystem key to use the
cleaned_path while leaving getOrPutPath, onLoad, and result.path unchanged so
suffixes like ?raw and ?url remain distinct.
- Around line 2647-2652: The code uses std.mem.lastIndexOfAny to strip only the
final delimiter, leaving query strings when multiple delimiters exist; change
the logic in the path_to_use computation to find the first occurrence of '?' or
'#' (use std.mem.indexOfAny on result.path), and when a match is found slice
result.path[0..index] to remove the entire trailing query/fragment; ensure the
exists check (bun.sys.exists) uses that stripped path_to_use and fall back to
result.path only if no delimiter is found.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 25fac64a-d129-4695-b4c4-ef0ffce7399d

📥 Commits

Reviewing files that changed from the base of the PR and between 18b0a9e and 0c55b3a.

📒 Files selected for processing (1)
  • src/bundler/bundle_v2.zig

Instead of lastIndexOfAny on the full path, find first ?/# only in
the filename portion (after last path separator). This correctly
handles both C#/main.js (# in directory preserved) and
file.html?env=browser#ssr (entire suffix stripped at first ?).
Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/bundler/bundle_v2.zig`:
- Around line 2647-2653: The current logic assumes the first '?' or '#' in the
basename is the suffix and trims at that point, causing incorrect disk paths
when filenames legitimately contain those characters; change the block that
computes path_to_use (the branch under is_file_namespace using basename_start,
bun.strings.indexOfAny, bun.sys.exists and result.path) to, after locating the
basename, search for all occurrences of '?' or '#' in the basename, iterate
those cut indices from rightmost to leftmost, and for each candidate build the
trimmed path and call bun.sys.exists until you find the first existing file—use
that path; if none exist, fall back to using result.path as-is (or the original
behavior when bun.sys.exists(result.path) is true).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7f2a95b2-c152-4e59-95ca-be749766bb6e

📥 Commits

Reviewing files that changed from the base of the PR and between 0c55b3a and bb91421.

📒 Files selected for processing (1)
  • src/bundler/bundle_v2.zig

Comment on lines +2647 to +2653
const path_to_use = if (is_file_namespace) blk: {
const basename_start = if (bun.path.lastIndexOfSep(result.path)) |sep| sep + 1 else 0;
if (bun.strings.indexOfAny(result.path[basename_start..], "?#")) |offset| {
break :blk if (bun.sys.exists(result.path))
result.path
else
result.path[0 .. basename_start + offset];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Don't assume the first ?/# in the basename is the appended suffix.

On POSIX, filenames can legitimately contain ? or #. With result.path = "/tmp/foo#bar.txt?env=browser", Line 2649 cuts the path to /tmp/foo, so plugin-resolved file paths with literal ?/# in the filename still resolve to the wrong disk path. Once the full path doesn't exist, try candidate cut points from the end of the basename back toward the start and use the first candidate that exists.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/bundler/bundle_v2.zig` around lines 2647 - 2653, The current logic
assumes the first '?' or '#' in the basename is the suffix and trims at that
point, causing incorrect disk paths when filenames legitimately contain those
characters; change the block that computes path_to_use (the branch under
is_file_namespace using basename_start, bun.strings.indexOfAny, bun.sys.exists
and result.path) to, after locating the basename, search for all occurrences of
'?' or '#' in the basename, iterate those cut indices from rightmost to
leftmost, and for each candidate build the trimmed path and call bun.sys.exists
until you find the first existing file—use that path; if none exist, fall back
to using result.path as-is (or the original behavior when
bun.sys.exists(result.path) is true).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Appending query string to .html import in a build.onResolve hook in a plugin crashes

1 participant