denoland/deno · error · TypeError

load hook must return { shortCircuit: true } or call nextLoa

Error message

load hook must return { shortCircuit: true } or call nextLoad

What it means

The same contract for the load hook of module.registerHooks(): a load hook must call nextLoad() or return an object with shortCircuit: true carrying the module source (for non-file schemes the runner itself returns { source: null, shortCircuit: true }). Returning undefined without chaining throws this TypeError from the hook runner.

Source

Thrown at ext/node/polyfills/01_require.js:857

          // transient IO) falls through to Rust default loading via
          // the load loop, which surfaces a clearer error if the
          // module truly cannot be loaded.
          return { source: null, shortCircuit: true };
        }
      }
      // For other schemes (data:, http(s):, etc.) we cannot synchronously
      // produce source here; fall through to Rust default loading.
      return { source: null, shortCircuit: true };
    }
    const hook = loadHooks[index++];
    let nextCalled = false;
    const wrappedNext = (u, c) => {
      nextCalled = true;
      return nextLoad(u, c);
    };
    const result = hook(loadUrl, currentContext, wrappedNext);
    if (!nextCalled && !result?.shortCircuit) {
      throw new TypeError(
        "load hook must return { shortCircuit: true } or call nextLoad",
      );
    }
    return result;
  }

  const result = nextLoad(fileUrl, context);
  return { result, effectiveUrl };
}

function _startEsmLoadLoop() {
  if (esmLoadLoopRunning) return;
  esmLoadLoopRunning = true;
  (async () => {
    while (true) {
      const pollPromise = op_module_hooks_poll_load();
      core.unrefOpPromise(pollPromise);
      const req = await pollPromise;

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. For URLs you handle, return { source, format, shortCircuit: true }
  2. End the hook body with `return nextLoad(url, context);`
  3. Keep hooks synchronous - registerHooks callbacks must not be async or return promises

Example fix

// before
module.registerHooks({
  load(url, context, nextLoad) {
    if (url.startsWith("virtual:")) {
      return { source: exportSourceFor(url), format: "module", shortCircuit: true };
    }
    // missing fallthrough -> TypeError
  },
});

// after
module.registerHooks({
  load(url, context, nextLoad) {
    if (url.startsWith("virtual:")) {
      return { source: exportSourceFor(url), format: "module", shortCircuit: true };
    }
    return nextLoad(url, context);
  },
});
Defensive patterns

Strategy: validation

Validate before calling

// Total load hook: handled URLs short-circuit with source, everything else chains.
const loadHook = (url: string, context: object, nextLoad: Function) =>
  url.startsWith("virtual:")
    ? { source: virtualSourceFor(url), format: "module", shortCircuit: true }
    : nextLoad(url, context);

module.registerHooks({ load: loadHook });

Type guard

function isLoadResult(v: unknown): v is { source: string | null; format?: string; shortCircuit?: boolean } {
  return v != null && typeof v === "object" && "source" in v;
}

Prevention

When it happens

Trigger: A load hook that handles only some URLs and forgets `return nextLoad(url, context)` on the rest; returning { source } without shortCircuit: true; returning a Promise from the synchronous hook.

Common situations: Custom virtual modules or source transforms (TS/Babel) wired via load hooks; porting async module.register()-style loaders to the synchronous registerHooks API without adjusting control flow.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/02d47bd502890b0d. Report an issue: GitHub.