#6162·pm2

Bun fork container uses require(), so an ESM entry containing top-level await cannot start

Author: rhwinterCreated Sep 21, 2026Updated Sep 21, 2026

What's going wrong?

lib/ProcessContainerForkBun.js loads the application with

javascript
require(process.env.pm_exec_path);   // line 25 on main

Bun can require() plain ESM, but it cannot require() a module whose graph contains top-level await — such a module is asynchronous, and require() of it throws. So any app started with interpreter: 'bun' fails at boot if it, or anything it imports, uses top-level await:

TypeError: require() async module "/path/to/initialize.js" is unsupported. use "await import()" instead.
    at require-in-the-middle/index.js:101:39
    at pm2/lib/ProcessContainerForkBun.js:27:1

The app then restart-loops, since the failure happens before it can listen.

lib/God/ForkMode.js routes any bun interpreter to that container:

javascript
else if (interpreter === 'bun' || RegExp('bun$').test(interpreter)) {
  args.push(path.resolve(path.dirname(module.filename), '..', 'ProcessContainerForkBun.js'));
}

The node container handles ESM; the bun one does not.

How to reproduce

javascript
// app.js  — package.json has "type": "module"
import http from 'node:http'

await new Promise((r) => setTimeout(r, 10))   // any top-level await will do

http.createServer((_req, res) => res.end('ok')).listen(3000)
javascript
// ecosystem.config.cjs
module.exports = { apps: [{ name: 'app', script: './app.js', interpreter: 'bun', exec_mode: 'fork' }] }

pm2 start ecosystem.config.cjs → the process restart-loops with the error above. Removing the top-level await makes it start.

Versions

pm2 6.x observed; the same require(process.env.pm_exec_path) is still present on main as of 7.0.4, so it should be unchanged there. Bun 1.4.2, node 24.8.0, Linux 6.1.0-21-amd64.

Suggested fix

Use a dynamic import in the bun container, which handles both sync and async module graphs:

javascript
import(process.env.pm_exec_path).catch((error) => {
  console.error(error)
  process.exit(1)
})

require() of a synchronous ESM module works in bun today, so switching to import() should not regress the currently-working case, and it removes a class of failures that only appears at boot in production.

Workaround, for anyone hitting this

Point script at a .cjs shim beside the real entry:

javascript
// initialize.bun.cjs
import('./initialize.js').catch((error) => {
  console.error(error)
  process.exit(1)
})

A .cjs file stays CommonJS inside a "type": "module" package, so pm2 can require() it, and the dynamic import inside handles the async graph. Verified working.