denoland/deno · error · ERR_INVALID_REPL_EVAL_CONFIG
ERR_INVALID_REPL_EVAL_CONFIG
ERR_INVALID_REPL_EVAL_CONFIG
Error message
Cannot specify both "breakEvalOnSigint" and "eval" for REPL
What it means
`repl.start()` accepts `breakEvalOnSigint` (abort the current evaluation on Ctrl+C) and `eval` (a custom function used to evaluate each line). They are mutually exclusive: the sigint-breaking mechanism only wraps the default eval path, so both Node and this polyfill throw ERR_INVALID_REPL_EVAL_CONFIG from the REPLServer constructor when both options are truthy.
Source
Thrown at ext/node/polyfills/repl.ts:705
this._isStandalone =
!!(options as Record<symbol, unknown>)[kStandaloneREPL];
this.useGlobal = !!useGlobal;
this.ignoreUndefined = !!ignoreUndefined;
this.replMode = replMode || REPL_MODE_SLOPPY;
this.underscoreAssigned = false;
this.last = undefined;
this.underscoreErrAssigned = false;
this.lastError = undefined;
this.breakEvalOnSigint = !!options.breakEvalOnSigint;
this.editorMode = false;
this[kContextId] = undefined;
this._initialPrompt = prompt as string;
// Stable resource name like Node's "REPL1"/"REPL2"/... - used as the
// filename for compiled scripts so stack traces match Node's output.
this._resourceName = getREPLResourceName();
if (this.breakEvalOnSigint && eval_) {
throw new ERR_INVALID_REPL_EVAL_CONFIG();
}
if ((options as Record<symbol, unknown>)[kStandaloneREPL]) {
_module.exports.repl = this;
} else {
// Non-standalone REPLs install the user-listener guard and route
// async errors via `process._fatalException`. The fatal-exception
// hook is global; the guard is refcounted so multiple concurrent
// REPLs co-exist and the process is left clean once the last exits.
// We use `_fatalException` (rather than process.on) so user code
// inspecting `process.listenerCount('uncaughtException')` sees 0,
// matching Node's domain-based REPL.
_installCaptureHook();
_addNewListenerGuard();
ArrayPrototypePush(_activeREPLs, this);
this._processListenersAttached = true;
this.once("exit", () => {
const idx = ArrayPrototypeIndexOf(_activeREPLs, this);View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Remove `breakEvalOnSigint: true` if you supply a custom `eval` function
- Drop the custom `eval` and use the default evaluator to keep Ctrl+C breaking
- Implement interruption yourself: set a flag from a SIGINT listener and check it inside the custom eval function
Example fix
// before
repl.start({ breakEvalOnSigint: true, eval: myEval });
// ERR_INVALID_REPL_EVAL_CONFIG
// after
repl.start({ eval: myEval }); // handle Ctrl+C inside myEval if needed Defensive patterns
Strategy: validation
Validate before calling
function startReplSafely(opts = {}) {
if (opts.breakEvalOnSigint && typeof opts.eval === 'function') {
throw new TypeError(
'breakEvalOnSigint and eval are mutually exclusive for repl.start()',
);
}
return repl.start(opts);
} Prevention
- Treat breakEvalOnSigint and eval as mutually exclusive in project docs and lint rules
- Centralize repl.start() options in one config object so conflicts are visible at review
When it happens
Trigger: Calling `repl.start({ breakEvalOnSigint: true, eval: myEvalFn })`, or constructing `new REPLServer(...)` with both options set.
Common situations: Custom REPLs built on a vm/global-eval function where Ctrl+C interruption is also wanted; merging options objects copied from two different examples; adding `breakEvalOnSigint` to existing config without removing a previously-set `eval`.
Related errors
- ERR_INVALID_ARG_TYPE
- ERR_INVALID_ARG_VALUE
- ERR_INCOMPATIBLE_OPTION_PAIR
- ERR_MISSING_OPTION
- ERR_INVALID_REPL_INPUT
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/dc72842e6f097b51.
Report an issue: GitHub.