rolldown/rolldown · error
Bundler is closed
Error message
Bundler is closed
What it means
After a bundler instance is closed (via close()/write/generate lifecycle completion), it can no longer be used. create_error_if_closed is a guard invoked at the start of write, generate, and scan, so any of these calls on a closed bundler throws 'Bundler is closed'.
Solutions
- Create a new Bundler instance for the additional build instead of reusing the closed one
- Reorder code so close() is only called after all write/generate/scan calls are done
- Use rolldown's watch API for rebuilds rather than manually re-driving a closed bundler
Example fix
// before await bundler.generate(); await bundler.close(); await bundler.generate(); // throws // after await bundler.generate(); await bundler.close(); const bundler2 = await rolldown(config); await bundler2.generate();
Defensive patterns
Strategy: try-catch
Validate before calling
if (bundler.isClosed && bundler.isClosed()) throw new Error('create a new bundler'); Type guard
const isUsable = (b) => !b.closed;
Try / catch
try { await bundler.generate(); } catch (e) { if (String(e).includes('Bundler is closed')) { bundler = await rolldown(config); await bundler.generate(); } else throw e; } Prevention
- Never reuse a bundler after close(); create a new one per build
- Call close() only in a final cleanup phase
- Use watch mode for repeated builds instead of manual lifecycles
When it happens
Trigger: Calling bundler.write(), bundler.generate(), or bundler.scan() after bundler.close() has completed, or after an implicit close from a prior completed lifecycle.
Common situations: Rebuilding with the same Bundler object after close (correct pattern is bundle.close() then a new Bundler, or watch mode); calling generate in both Node and browser contexts on a shared instance; race conditions where close finishes before a queued generate.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Dev engine is closed
- FileEmitter: failed to send AddEntryModule message - module…
- function err(n) { ... t = t !== e ? new r(n, t) : n ..…
- function err(r) { ... t = s ? new…
- ${napi_error.reason}
AI-assisted analysis of rolldown/rolldown@91b44b9d7b (2026-09-07).
Data as JSON: /api/errors/14a21be24cf09f99.
Report an issue: GitHub.
Appendix: source
Thrown at crates/rolldown/src/bundler/bundler.rs:49
) -> BuildResult<Self> {
let bundle_factory = BundleFactory::new(crate::BundleFactoryOptions {
bundler_options: options,
plugins,
session: None,
disable_tracing_setup: true,
})?;
Ok(Self {
bundle_factory,
session: rolldown_devtools::Session::dummy(),
cache: ScanStageCache::default(),
closed: false,
})
}
pub(super) fn create_error_if_closed(&self) -> BuildResult<()> {
if self.closed {
Err(anyhow::anyhow!("Bundler is closed"))?;
}
Ok(())
}
// Implementation is split across multiple files:
// - Normal build operations and lifecycle: `impl_bundler_build.rs`
// - Getter/accessor methods: `impl_bundler_getter.rs`
// - Incremental build methods: `impl_bundler_incremental_build.rs`
// - HMR methods: `impl_bundler_hmr.rs`
}
fn _test_bundler() {
fn assert_send(_foo: impl Send) {}
let mut bundler = Bundler::new(BundlerOptions::default()).expect("Failed to create bundler");
let write_fut = bundler.write();
assert_send(write_fut);
let mut bundler = Bundler::new(BundlerOptions::default()).expect("Failed to create bundler");
let generate_fut = bundler.generate();View on GitHub (pinned to 91b44b9d7b)