oven-sh/bun · critical · Error

Could not find BUN_FOREACH_ESM_AND_CJS_NATIVE_MODULE in Nati

Error message

Could not find BUN_FOREACH_ESM_AND_CJS_NATIVE_MODULE in NativeModuleList.h. Knowing native module IDs is a part of the codegen process.

What it means

internal-module-registry-scanner.ts reads src/jsc/modules/NativeModuleList.h and parses every `macro("id", Enum)` entry inside BUN_FOREACH_ESM_AND_CJS_NATIVE_MODULE to assign native module IDs used throughout codegen. If zero entries matched (nextNativeModuleId === 0), the header was moved, renamed, or reformatted, and codegen cannot proceed without those IDs.

Source

Thrown at src/codegen/internal-module-registry-scanner.ts:56

  const nativeModuleH = fs.readFileSync(path.join(basedir, "../jsc/modules/NativeModuleList.h"), "utf8");
  for (const [_, idString, enumValue] of nativeModuleH.matchAll(/macro\((.*?),(.*?)\)/g)) {
    const processedIdString = JSON.parse(idString.trim().replace(/_s$/, ""));
    const processedEnumValue = enumValue.trim();
    const processedNumericId = nextNativeModuleId++;
    nativeModuleIds[processedIdString] = processedNumericId;
    nativeModuleEnums[processedIdString] = processedEnumValue;
    nativeModuleEnumToId[processedEnumValue] = processedNumericId;
  }

  const nativeStartIndex = moduleList.length;

  for (const [id] of Object.entries(nativeModuleIds)) {
    moduleList.push(id);
    internalRegistry.set(id, moduleList.length - 1);
  }

  if (nextNativeModuleId === 0) {
    throw new Error(
      "Could not find BUN_FOREACH_ESM_AND_CJS_NATIVE_MODULE in NativeModuleList.h. Knowing native module IDs is a part of the codegen process.",
    );
  }

  function codegenRequireId(id: string) {
    return `(__intrinsic__getInternalField(__intrinsic__internalModuleRegistry, ${id}) || __intrinsic__createInternalModuleById(${id}))`;
  }

  const requireTransformer = (specifier: string, from: string) => {
    const directMatch = internalRegistry.get(specifier);
    if (directMatch) return codegenRequireId(`${directMatch}/*${specifier}*/`);

    const relativeMatch =
      resolveSyncOrNull(specifier, path.join(basedir, path.dirname(from))) ?? resolveSyncOrNull(specifier, basedir);

    const suffix =
      'Only files in "src/js" besides "src/js/builtins" can be imported here. Note that the "node:" or "bun:" prefix is required here.';

View on GitHub (pinned to 8c5296ac45)

Solutions

  1. Restore src/jsc/modules/NativeModuleList.h at that exact path with the `#define BUN_FOREACH_ESM_AND_CJS_NATIVE_MODULE(macro)` block.
  2. Keep entries in the `macro("fs", Fs)` shape — string literal ID first, enum identifier second — so the scanner regex matches.
  3. If the header must move, update the path in internal-module-registry-scanner.ts (line 38) in the same commit.
Defensive patterns

Strategy: validation

Validate before calling

const headerPath = path.join(basedir, "../jsc/modules/NativeModuleList.h");
const header = fs.readFileSync(headerPath, "utf8");
if (!/macro\((.*?),(.*?)\)/.test(header)) {
  throw new Error(`${headerPath}: no macro(id, Enum) entries found — header was moved or reformatted`);
}

Prevention

When it happens

Trigger: The file src/jsc/modules/NativeModuleList.h is missing, the macro BUN_FOREACH_ESM_AND_CJS_NATIVE_MODULE was renamed, or the entries no longer match the /macro\((.*?),(.*?)\)/ pattern (e.g. changed to macro(id, Enum) without string quotes or a different helper name).

Common situations: Reformatting or restructuring the JSC module list header; moving it into another directory during a refactor; a bad merge that dropped the #define block.

Related errors


AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16). Data as JSON: /api/errors/6201e9b7be991f50. Report an issue: GitHub.