denoland/deno · error · TypeError

ERR_MODULE_LINK_MISMATCH

ERR_MODULE_LINK_MISMATCH

Error message

Expected ${requests.length} modules, got ${modules.length}

What it means

Thrown by SourceTextModule#linkRequests(modules) when the provided array length differs from module.moduleRequests.length (vm.js:793-798). Every static import/exports request in the source must be answered with exactly one module, in the same order as moduleRequests.

Source

Thrown at ext/node/polyfills/vm.js:795

  }

  get dependencySpecifiers() {
    if (this[kDependencySpecifiers] === undefined) {
      this[kDependencySpecifiers] = ObjectFreeze(
        ArrayPrototypeMap(this[kModuleRequests], (r) => r.specifier),
      );
    }
    return this[kDependencySpecifiers];
  }

  linkRequests(modules) {
    if (this.status !== "unlinked") {
      throw new ERR_VM_MODULE_STATUS("must be unlinked");
    }
    validateArray(modules, "modules");
    const requests = this[kModuleRequests];
    if (modules.length !== requests.length) {
      throw new ERR_MODULE_LINK_MISMATCH(
        `Expected ${requests.length} modules, got ${modules.length}`,
      );
    }
    // Validate each provided module first (type + context), then check for
    // cache-key collisions: two requests sharing (specifier, attributes)
    // must map to the same module instance, matching Node's V8 binding
    // behavior. We use this single pass to keep the error precedence
    // identical to Node's tests.
    const seen = new SafeMap();
    const specifiers = [];
    const wraps = [];
    for (let i = 0; i < modules.length; i++) {
      const m = modules[i];
      if (!isModule(m)) {
        throw new ERR_VM_MODULE_NOT_MODULE();
      }
      if (m.context !== this[kContext]) {
        throw new ERR_VM_MODULE_DIFFERENT_CONTEXT();

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Derive the array from the module itself: `mod.linkRequests(mod.moduleRequests.map(r => resolveModule(r.specifier)))` so lengths always match
  2. Log `mod.moduleRequests.map(r => r.specifier)` and diff against the modules array before calling
  3. Regenerate the dependency list whenever sourceText changes; never hardcode the count
  4. Treat a length mismatch as a bug signal in the bundler/generator, not a runtime condition to catch

Example fix

// before
mod.linkRequests([modA]); // source has two imports -> mismatch
// after
const mods = mod.moduleRequests.map((r) => resolveModule(r.specifier));
mod.linkRequests(mods);
Defensive patterns

Strategy: validation

Validate before calling

const mods = mod.moduleRequests.map((r) => resolve(r.specifier, r.attributes));
if (mods.length !== mod.moduleRequests.length) throw new Error('resolver dropped entries');
mod.linkRequests(mods);

Try / catch

try { mod.linkRequests(mods); }
catch (e) {
  if (e.code === 'ERR_MODULE_LINK_MISMATCH') {
    const want = mod.moduleRequests.map((r) => r.specifier);
    throw new Error(`resolved ${mods.length}, source wants ${want.length}: [${want}]`);
  } else throw e;
}

Prevention

When it happens

Trigger: Source has two imports (`import 'a'; import 'b'`) but the modules array has one entry; passing modules in a different pipeline that filtered or deduplicated them; hand-writing the array and forgetting a request; a build step generating the module source after the array was computed, adding or removing an import; passing an empty array for a module that does have imports.

Common situations: Plugin loaders where the source text is templated per environment but the dependency array is static; tests that copy a modules array between test cases whose sources differ; caching moduleRequests from an older version of the source; assuming import.meta or dynamic import() entries appear in moduleRequests (only static requests do, which shifts expected counts).

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/a9b308da0c284530. Report an issue: GitHub.