denoland/deno · error · ERR_INVALID_REPL_INPUT

ERR_INVALID_REPL_INPUT

ERR_INVALID_REPL_INPUT

Error message

Listeners for `uncaughtException` cannot be used in the REPL

What it means

Node's REPL installs its own uncaughtException handler so an error in evaluated code does not kill the interactive session. To keep that handler from being shadowed, Node (and Deno's node:repl polyfill) reject any additional `uncaughtException` listener registered while a non-standalone REPL is active. The polyfill enforces this with a refcounted `newListener` guard that only accepts functions named `_replUncaughtHandler`, so the offending `process.on()` call throws ERR_INVALID_REPL_INPUT synchronously.

Source

Thrown at ext/node/polyfills/repl.ts:184

  p.setUncaughtExceptionCaptureCallback(null);
  if (typeof _prevCaptureCallback === "function") {
    p.setUncaughtExceptionCaptureCallback(_prevCaptureCallback);
  }
  _prevCaptureCallback = null;
}

// Node's REPL refuses user-installed `uncaughtException` listeners so the
// REPL's own handler isn't shadowed. Mirror that with a counted
// `newListener` guard installed once and removed when the last non-
// standalone REPL exits. The REPL's own handler is named
// `_replUncaughtHandler`, matching the only allowed function name.
let _newListenerGuardCount = 0;
function _newListenerGuard(event: string, listener: any) {
  if (
    event === "uncaughtException" &&
    listener && listener.name !== "_replUncaughtHandler"
  ) {
    throw new ERR_INVALID_REPL_INPUT(
      "Listeners for `uncaughtException` cannot be used in the REPL",
    );
  }
}
function _addNewListenerGuard() {
  if (_newListenerGuardCount++ === 0) {
    process.prependListener("newListener", _newListenerGuard);
  }
}
function _removeNewListenerGuard() {
  if (--_newListenerGuardCount === 0) {
    process.removeListener("newListener", _newListenerGuard);
  }
}

const reAtFrame = new SafeRegExp(/^\s+at\s/);
const reExtFrame = new SafeRegExp(/\bext:[a-z_]+\//);
const reNodeParenFrame = new SafeRegExp(/\(node:[a-z_]+:/);

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Move the `process.on('uncaughtException', ...)` registration out of REPL-evaluated code into the entry-point script
  2. Run the code as a file (`deno run app.ts`) instead of pasting it into the REPL
  3. If you must stay interactive, wrap the risky section in try/catch instead of registering a global handler
  4. As a discouraged last resort, name the handler `_replUncaughtHandler` — it bypasses the guard but shadows the REPL's handler

Example fix

// before (typed in the REPL)
process.on('uncaughtException', (e) => console.error(e));
// ERR_INVALID_REPL_INPUT

// after (in the REPL)
try { risky(); } catch (e) { console.error(e); }
// or: put process.on('uncaughtException', ...) in the script and run `deno run app.ts`
Defensive patterns

Strategy: try-catch

Try / catch

try {
  process.on('uncaughtException', handler);
} catch (e) {
  if (e.code === 'ERR_INVALID_REPL_INPUT') {
    // Running inside a REPL: fall back to local try/catch handling
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Running `process.on('uncaughtException', handler)` (or addListener/prependListener) inside a `deno` REPL session, or inside code evaluated by a non-standalone `repl.start()` — any listener whose function name is not `_replUncaughtHandler` triggers the throw.

Common situations: Pasting a script that installs a global crash handler into the REPL to debug it; `.load`ing a test-setup file that registers uncaughtException listeners; migrating CLI tools that assume they run as plain scripts rather than inside a REPL.

Related errors


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