Skip to content

bundler: fix dev server crash when a CSS rebuild fails import resolution - #31905

Merged
Jarred-Sumner merged 2 commits into
mainfrom
farm/11f30366/fix-css-chunk-assert
Jun 6, 2026
Merged

bundler: fix dev server crash when a CSS rebuild fails import resolution#31905
Jarred-Sumner merged 2 commits into
mainfrom
farm/11f30366/fix-css-chunk-assert

Conversation

@robobun

@robobun robobun commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Fixes #31903

Repro

Debug builds panic during a dev server hot reload when a CSS file parses but fails import resolution (for example an unterminated url( that tokenizes to a URL, or a url(./missing.png) pointing at a file that does not exist):

panic: assertion failed: !chunk.content.is_css()
bun_bundler::linker_context::generate_chunks_in_parallel::generate_chunks_in_parallel::<true>
    src/bundler/linker_context/generateChunksInParallel.rs:132
<bun_bundler::bundle_v2::BundleV2>::finish_from_bake_dev_server

Both of these existing tests crash on main with DevServer crashed while waiting for hot reload:

bun bd test test/bake/dev/css.test.ts -t "syntax error crash"
bun bd test test/bake/dev/css.test.ts -t "css import before create project relative"

Cause

graph.css_file_count is only incremented when a parse task completes successfully with a CSS AST. When resolution fails after a successful CSS parse, run_resolution_for_parse_task converts the result to an error, and (since the leak fixes in #30875) moved the parsed stylesheet onto the graph row so teardown could free it. That comment assumed the linker never runs after such an error, but the dev server intentionally proceeds with failed files, and finish_from_bake_dev_server treats a populated css slot as "successfully parsed CSS" when discovering CSS entry points. The failed file was therefore re-added as a CSS entry point and got a CSS chunk while css_file_count stayed 0, tripping the debug_assert!(!chunk.content.is_css()) in generate_chunks_in_parallel. In release builds the assert compiles out and the CSS dedup/prepare pass is silently skipped for that chunk. The Zig implementation never stored the CSS AST on this path, so the dev server's invariant held there.

The parked AST also diverged from the reference in find_imported_files_in_css_order (a failed file's stale rules could be included in another chunk's import order) and scan_css_imports.

Fix

Drop the stylesheet at the failure site in run_resolution_for_parse_task instead of parking it on the graph row. The graph row stays None for failed files, restoring the invariant every dev server consumer of the css column relies on, and the stylesheet's non-arena allocations are still freed (same drop_in_place the teardown pass uses).

Verification

  • New test test/bake/dev/css.test.ts "css url resolve error on hot reload is recoverable" fails on the unfixed build with DevServer crashed while waiting for hot reload and passes with the fix: a connected client asserts the exact error overlay text and the route returns 500, then recovery back to a 200 is checked after the error is fixed. Exercising the recovery with the client still connected hits a separate, pre-existing HMR patch bug (the HTML route module is shipped without the route-reload flag), tracked in Dev server ships HTML route module as a JS HMR module when a failed CSS root recovers #31908.
  • Full test/bake/dev/css.test.ts (14 tests, including the two previously-crashing ones), test/bake/dev/html.test.ts, test/bake/dev/bundle.test.ts, test/bake/dev/hot.test.ts, and test/bake/dev/plugins.test.ts pass on the debug/ASAN build.
  • Non-dev path (Bun.build with a CSS entry whose url() fails to resolve) still reports Could not resolve and is clean under ASAN.

@robobun

robobun commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Need an answer fast? Review this PR in Change Stack to ask focused questions about the PR or a changed range.

Review Change Stack

Walkthrough

When CSS import resolution fails during parsing, the bundler now drops the parsed stylesheet instead of storing it in the graph, preventing failed files from being treated as valid CSS modules. A new HMR test verifies that a CSS URL resolution error produces a 500 and that fixing the CSS restores a 200 response.

Changes

CSS Resolution Error Handling

Layer / File(s) Summary
CSS resolution error handling in bundler
src/bundler/bundle_v2.rs
On import resolution failure for a parse task, result.ast.css is taken and dropped instead of being written into graph.ast.items_css, leaving the graph's CSS slot empty for failed files.
Hot reload test for CSS resolution errors
test/bake/dev/css.test.ts
Adds css url resolve error on hot reload is recoverable devTest: verifies initial 200, triggers rebuild failure with missing asset (expects 500 and CSS URL error), then fixes CSS and expects 200 recovery.

Possibly related issues

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description comprehensively covers the root cause, fix, and verification, but it does not follow the repository's required template structure with explicit 'What does this PR do?' and 'How did you verify your code works?' headings. Reorganize the description to explicitly match the template sections: add 'What does this PR do?' heading before the fix explanation and 'How did you verify your code works?' heading before the verification details.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: fixing a dev server crash when CSS rebuild fails import resolution.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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.

Comment thread test/bake/dev/css.test.ts Outdated
Comment thread src/bundler/bundle_v2.rs
robobun added 2 commits June 6, 2026 00:10
…king it on the graph

When a CSS file parses successfully but one of its import records fails
to resolve, the parse result is converted to an error and the parsed
stylesheet was moved onto the graph row so teardown could free it. The
dev server proceeds with failed files and treats a populated css slot
as a successfully parsed CSS file, so the failed file was re-added as a
CSS entry point in finish_from_bake_dev_server. That produced a CSS
chunk while graph.css_file_count stayed 0, tripping
debug_assert!(!chunk.content.is_css()) in generate_chunks_in_parallel
and crashing the dev server on hot reload.

Drop the stylesheet at the failure site instead. The graph row stays
None for failed files, matching the reference behavior, and the leak
fix that introduced the parking is preserved.

Fixes #31903
Connect a client so the errors option is actually validated against the
overlay, and update the stale comment above the import_records
preservation to scope the no-linker claim to non-dev builds.

Recovery is asserted via fetch without a connected client: the recovery
patch ships the HTML route as a JS module without the route-reload flag,
which trips a client-runtime debug assert. Tracked in #31908.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/bundler/bundle_v2.rs`:
- Around line 5737-5749: The code currently only drops the parsed stylesheet in
the resolve_import_records error path (the block using result.ast.css.take() and
unsafe drop_in_place) but not when async plugin onResolve failures occur,
leaving items_css[source_index].is_some() set and allowing
finish_from_bake_dev_server() to re-add a failed CSS entry point; extract the
stylesheet invalidation/drop logic into a shared helper (e.g.,
invalidate_failed_css or clear_parsed_stylesheet) and call it from both the
resolve_import_records error branch and the plugin onResolve error branch so
that whenever a CSS resolution fails (either via last_error or onResolve), the
AST/css arena is removed and items_css[source_index] is cleared consistently to
prevent re-adding failed CSS chunks (update calls in resolve_import_records(),
the onResolve error handling site, and ensure finish_from_bake_dev_server() will
no longer see a populated css slot).
🪄 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: 310e14e6-d86a-442a-9bee-a3888b60310f

📥 Commits

Reviewing files that changed from the base of the PR and between 50fd91a and c3f60a4.

📒 Files selected for processing (2)
  • src/bundler/bundle_v2.rs
  • test/bake/dev/css.test.ts

Comment thread src/bundler/bundle_v2.rs
@robobun

robobun commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator Author

CI status for the merging maintainer: the remaining red lanes on build 60948 are not related to this diff.

Locally on the debug/ASAN build: all 14 tests in test/bake/dev/css.test.ts pass, including the two that crash on main and the new regression test, plus the html/bundle/hot/plugins bake suites.

@Jarred-Sumner
Jarred-Sumner merged commit 90589f9 into main Jun 6, 2026
70 of 78 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the farm/11f30366/fix-css-chunk-assert branch June 6, 2026 17:33
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.

Dev server debug assert: !chunk.content.is_css() when a CSS rebuild fails to parse

2 participants