From 0c9c017f53b472639a8cd3bd5b56369aaea70ea3 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 9 Mar 2026 14:30:05 +0100 Subject: [PATCH] repl: handle exceptions from async context after close a9da9ffc04c923f383 recently restructured async context handling in the REPL source so that it no longer uses the domain module, and instead performs its own async context tracking. That change was not intended to be breaking, but it affected behavior for uncaught exceptions thrown after the REPL was closed. Those would now be treated as uncaught exceptions on the process level, which is probably not intentional in most situations. While it's understandably not great that we handle execptions after closing the REPL instance, I am confident that it's best to restore the previous behavior for now and add more granular handling options separately and intentionally in a (potentially semver-major) follow-up change. Refs: https://github.com/nodejs/node/pull/61227 --- lib/repl.js | 7 +++++- ...pl-uncaught-exception-after-input-ended.js | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-repl-uncaught-exception-after-input-ended.js diff --git a/lib/repl.js b/lib/repl.js index 3c33c73c413006..e747cc581cc8eb 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -195,7 +195,12 @@ function setupExceptionCapture() { process.addUncaughtExceptionCaptureCallback((err) => { const store = replContext.getStore(); - if (store?.replServer && !store.replServer.closed) { + // TODO(addaleax): Add back a `store.replServer.closed` check here + // as a semver-major change. + // This check may need to allow for an opt-out, since the change in + // behavior could lead to DoS vulnerabilities (e.g. in the case of + // the net-based REPL described in our docs). + if (store?.replServer) { store.replServer._handleError(err); return true; // We handled it } diff --git a/test/parallel/test-repl-uncaught-exception-after-input-ended.js b/test/parallel/test-repl-uncaught-exception-after-input-ended.js new file mode 100644 index 00000000000000..1e2ca86a9f079c --- /dev/null +++ b/test/parallel/test-repl-uncaught-exception-after-input-ended.js @@ -0,0 +1,23 @@ +'use strict'; +const common = require('../common'); +const { start } = require('node:repl'); +const { PassThrough } = require('node:stream'); +const assert = require('node:assert'); + +// This test verifies that uncaught exceptions in the REPL +// do not bring down the process, even if stdin may already +// have been ended at that point (and the REPL closed as +// a result of that). +const input = new PassThrough(); +const output = new PassThrough().setEncoding('utf8'); +start({ + input, + output, + terminal: false, +}); + +input.end('setImmediate(() => { throw new Error("test"); });\n'); + +setImmediate(common.mustCall(() => { + assert.match(output.read(), /Uncaught Error: test/); +}));