Repository navigation
π Bug Report β Runtime APIs: require("node:process") fails with "No such module" while ESM import succeedsΒ #7978
Description
Summary
With nodejs_compat enabled, both require("process") and require("node:process") throw:
Error: No such module "node:process".
This occurs with both:
require()inside acommonJsModule.- The
requirefunction returned bycreateRequire()fromnode:module.
The thrown error has no code property (e.code === undefined).
However, the equivalent ES module import works:
import process from "node:process";The imported object is identical to globalThis.process.
Other Node.js builtins tested resolve successfully through require(), including:
util, perf_hooks, path, module, url, worker_threads, fs, os, crypto, tty, events, async_hooks, node:http, node:https, and child_process.
Adding either enable_nodejs_process_v2 or disable_nodejs_process_v2 to the compatibility flags does not change the behavior.
Expected behavior
Both require("process") and require("node:process") should return the same object as:
import process from "node:process";That object should also be identical to globalThis.process, consistent with Node.js behavior and with how other builtins resolve in workerd.
If a builtin cannot be loaded through require(), the resulting error should at least have a meaningful code property, such as MODULE_NOT_FOUND or ERR_UNKNOWN_BUILTIN_MODULE, so callers can identify and handle the failure.
Impact
Libraries that load process through require() during module evaluation fail to initialize.
One affected example is the Node.js build of @vercel/otel (version 2.1.3).
In a Next.js application:
- Next.js bundles
@vercel/otelinto the application'sinstrumentation.ts. - During module evaluation, the library's host detector calls
createRequire(import.meta.url)("process"). - This throws because
processcannot be resolved throughrequire(). - The OpenTelemetry initialization fails, and Next.js propagates the error from its instrumentation hook without an error code.
- Consequently, every request to the affected application fails with HTTP 500.
This makes the issue more than a compatibility discrepancy: it can prevent an otherwise functioning application from serving requests.
Reproduction
Environment
- Runtime:
workerd 1.20260917.1 - Package: npm
workerd - Platform:
darwin-arm64 - Compatibility date:
2026-09-15 - Compatibility flags:
["nodejs_compat"]
Create the following three files.
config.capnp
using Workerd = import "/workerd/workerd.capnp";
const config :Workerd.Config = (
services = [ (name = "main", worker = .w) ],
sockets = [ (name = "http", address = "127.0.0.1:18790", http = (), service = "main") ],
);
const w :Workerd.Worker = (
modules = [
(name = "index.mjs", esModule = embed "index.mjs"),
(name = "app.cjs", commonJsModule = embed "app.cjs"),
],
compatibilityDate = "2026-09-15",
compatibilityFlags = ["nodejs_compat"],
);index.mjs
import app from './app.cjs';
import esmProcess from 'node:process';
export default {
async fetch() {
return Response.json({
esmImport: typeof esmProcess + ':same-as-global=' + (esmProcess === globalThis.process),
cjs: app.run(),
});
},
};app.cjs
module.exports.run = function run() {
const out = {};
for (const name of ['process', 'node:process', 'fs']) {
try {
out[`require(${name})`] = 'ok:' + typeof require(name);
} catch (e) {
out[`require(${name})`] = `throws ${e.message} (code: ${e.code})`;
}
}
const created = require('node:module').createRequire('file:///bundle/index.js');
for (const name of ['process', 'node:process', 'fs']) {
try {
out[`createRequire(${name})`] = 'ok:' + typeof created(name);
} catch (e) {
out[`createRequire(${name})`] = `throws ${e.message} (code: ${e.code})`;
}
}
return out;
};Run
Start workerd:
npx workerd serve config.capnpThen send a request:
curl http://127.0.0.1:18790/Actual output
{
"esmImport": "object:same-as-global=true",
"cjs": {
"require(process)": "throws No such module \"node:process\". (code: undefined)",
"require(node:process)": "throws No such module \"node:process\". (code: undefined)",
"require(fs)": "ok:object",
"createRequire(process)": "throws No such module \"node:process\". (code: undefined)",
"createRequire(node:process)": "throws No such module \"node:process\". (code: undefined)",
"createRequire(fs)": "ok:object"
}
}The output confirms that:
node:processresolves correctly through ESM import and is identical toglobalThis.process.- Both
processandnode:processfail throughrequire()andcreateRequire(). - Another builtin (
fs) resolves successfully through both mechanisms. - The errors have no
codeproperty.
The same failure occurs when either enable_nodejs_process_v2 or disable_nodejs_process_v2 is added to the compatibility flags.
Workaround
As a temporary workaround, we wrap createRequire() so that the returned require function:
- Returns
globalThis.processfor"process"and"node:process". - Delegates all other module specifiers to the original
requirefunction.
This avoids the failure in the affected library, but requires patching module resolution behavior that should work natively under nodejs_compat.
Activity
kevinlupera commentedon Oct 26, 2026
I would like to work on this.
From reading the source, the cause looks clear.
node:processhas no registration of its own. It has to be redirected tonode-internal:public_processornode-internal:legacy_process, depending onenable_nodejs_process_v2. On the legacy module registry, that redirect happens for static imports (jsg/modules.c++,resolveCallback) and for dynamic imports (jsg/modules.h). It is missing from bothrequire()paths:CommonJsModuleContext::require()inapi/commonjs.c++ModuleUtil::createRequire()inapi/node/module.c++Both end in
No such module "node:process"., which matches the report.Plan: apply the same redirect in both paths, and add tests for
require("process")andrequire("node:process")with the flag on and off. The tests will check that the result is identical toglobalThis.process. I would leave the missingcodeon the error for a separate change, unless you prefer it here.fix(modules): redirect node:process in require() on the legacy registry