Skip to content

Conversation

@mcollina
Copy link
Member

@mcollina mcollina commented Dec 30, 2025

Replace domain-based error handling with AsyncLocalStorage and setUncaughtExceptionCaptureCallback. This removes REPL's dependency on the deprecated domain module while preserving existing behavior and allowing the domain module to still be used within REPL sessions.

Changes

  • Use AsyncLocalStorage to track REPL instance context
  • Add addUncaughtExceptionCaptureCallback API for auxiliary callbacks, allowing REPL and domain to coexist
  • REPL checks for active domain before handling errors locally
  • Remove mutual exclusivity between domain and setUncaughtExceptionCaptureCallback

@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. repl Issues and PRs related to the REPL subsystem. labels Dec 30, 2025
@avivkeller avivkeller added the semver-major PRs that contain breaking changes and should be released in the next major version. label Jan 1, 2026
mcollina added a commit to mcollina/node that referenced this pull request Jan 10, 2026
Replace the domain-based error handling with AsyncLocalStorage and
setUncaughtExceptionCaptureCallback. This removes the REPL's dependency
on the deprecated domain module while preserving all existing behavior:

- Synchronous errors during eval are caught and displayed
- Async errors (setTimeout, promises, etc.) are caught via the
  uncaught exception capture callback
- Top-level await errors are caught and displayed
- The REPL continues operating after errors
- Multiple REPL instances can coexist with errors routed correctly

Changes:
- Use AsyncLocalStorage to track which REPL instance owns an async
  context, replacing domain's automatic async tracking
- Add setupExceptionCapture() to install
  setUncaughtExceptionCaptureCallback for catching async errors and
  routing them to the correct REPL
- Extract error handling logic into REPLServer.prototype._handleError()
- Wrap eval execution in replContext.run() for async context tracking
- Update newListener protection to check AsyncLocalStorage context
- Throw ERR_INVALID_ARG_VALUE if options.domain is passed

PR-URL: nodejs#61227
@mcollina mcollina force-pushed the repl/remove-domain-dependency branch from 6ef82e6 to e86555a Compare January 10, 2026 10:35
@mcollina mcollina marked this pull request as ready for review January 10, 2026 10:35
@codecov
Copy link

codecov bot commented Jan 10, 2026

Codecov Report

❌ Patch coverage is 94.95798% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.74%. Comparing base (f6464c5) to head (6125d23).
⚠️ Report is 61 commits behind head on main.

Files with missing lines Patch % Lines
lib/repl.js 94.62% 10 Missing ⚠️
lib/internal/process/execution.js 95.74% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #61227      +/-   ##
==========================================
- Coverage   89.76%   89.74%   -0.03%     
==========================================
  Files         673      675       +2     
  Lines      203944   204585     +641     
  Branches    39191    39315     +124     
==========================================
+ Hits       183080   183600     +520     
- Misses      13194    13250      +56     
- Partials     7670     7735      +65     
Files with missing lines Coverage Δ
lib/domain.js 98.33% <100.00%> (-0.05%) ⬇️
lib/internal/bootstrap/node.js 98.97% <100.00%> (+<0.01%) ⬆️
lib/internal/process/execution.js 94.55% <95.74%> (+0.37%) ⬆️
lib/repl.js 93.90% <94.62%> (-0.23%) ⬇️

... and 63 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mcollina
Copy link
Member Author

@nodejs/tsc this needs more reviews

@aduh95
Copy link
Contributor

aduh95 commented Jan 10, 2026

There are linter and test failures to address

mcollina added a commit to mcollina/node that referenced this pull request Jan 11, 2026
Replace the domain-based error handling with AsyncLocalStorage and
setUncaughtExceptionCaptureCallback. This removes the REPL's dependency
on the deprecated domain module while preserving all existing behavior:

- Synchronous errors during eval are caught and displayed
- Async errors (setTimeout, promises, etc.) are caught via the
  uncaught exception capture callback
- Top-level await errors are caught and displayed
- The REPL continues operating after errors
- Multiple REPL instances can coexist with errors routed correctly

Changes:
- Use AsyncLocalStorage to track which REPL instance owns an async
  context, replacing domain's automatic async tracking
- Add setupExceptionCapture() to install
  setUncaughtExceptionCaptureCallback for catching async errors and
  routing them to the correct REPL
- Extract error handling logic into REPLServer.prototype._handleError()
- Wrap eval execution in replContext.run() for async context tracking
- Update newListener protection to check AsyncLocalStorage context
- Throw ERR_INVALID_ARG_VALUE if options.domain is passed

PR-URL: nodejs#61227
@mcollina mcollina force-pushed the repl/remove-domain-dependency branch from e86555a to 855468d Compare January 11, 2026 12:46
@mcollina
Copy link
Member Author

@aduh95 done

@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. label Jan 12, 2026
@github-actions github-actions bot added request-ci-failed An error occurred while starting CI via request-ci label, and manual interventon is needed. and removed request-ci Add this label to start a Jenkins CI on a PR. labels Jan 12, 2026
@github-actions
Copy link
Contributor

Failed to start CI
   ⚠  Commits were pushed since the last approving review:
   ⚠  - repl: remove dependency on domain module
   ✘  Refusing to run CI on potentially unsafe PR
https://github.com/nodejs/node/actions/runs/20913460585

Copy link
Member

@marco-ippolito marco-ippolito left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with green CI

@mcollina mcollina added semver-minor PRs that contain new features and should be released in the next minor version. baking-for-lts PRs that need to wait before landing in a LTS release. dont-land-on-v20.x PRs that should not land on the v20.x-staging branch and should not be released in v20.x. dont-land-on-v22.x PRs that should not land on the v22.x-staging branch and should not be released in v22.x. and removed semver-major PRs that contain breaking changes and should be released in the next major version. labels Jan 13, 2026
@mcollina
Copy link
Member Author

I’ve updated this PR and made it minor by adding a new API and make it backward compatible with using domain.

I would wait for backporting this.

@marco-ippolito marco-ippolito added the needs-citgm PRs that need a CITGM CI run. label Jan 14, 2026
Replace the domain-based error handling with AsyncLocalStorage and
setUncaughtExceptionCaptureCallback. This removes the REPL's dependency
on the deprecated domain module while preserving all existing behavior:

- Synchronous errors during eval are caught and displayed
- Async errors (setTimeout, promises, etc.) are caught via the
  uncaught exception capture callback
- Top-level await errors are caught and displayed
- The REPL continues operating after errors
- Multiple REPL instances can coexist with errors routed correctly

Changes:
- Use AsyncLocalStorage to track which REPL instance owns an async
  context, replacing domain's automatic async tracking
- Add setupExceptionCapture() to install
  setUncaughtExceptionCaptureCallback for catching async errors and
  routing them to the correct REPL
- Extract error handling logic into REPLServer.prototype._handleError()
- Wrap eval execution in replContext.run() for async context tracking
- Update newListener protection to check AsyncLocalStorage context
- Throw ERR_INVALID_ARG_VALUE if options.domain is passed

PR-URL: nodejs#61227
@mcollina mcollina force-pushed the repl/remove-domain-dependency branch from 74ba1a5 to 90dda43 Compare February 5, 2026 22:58
Add auxiliary callback mechanism to setUncaughtExceptionCaptureCallback
to allow REPL and domain module to coexist. The REPL uses the new
addUncaughtExceptionCaptureCallback API which doesn't conflict with
domain's use of the primary callback.

- Add addUncaughtExceptionCaptureCallback for non-exclusive callbacks
- Update REPL to check for active domain before handling errors
- Remove mutual exclusivity checks from domain module
- Restore test-repl-domain.js test
- Update domain coexistence tests
@mcollina mcollina force-pushed the repl/remove-domain-dependency branch from 90dda43 to 045d98f Compare February 7, 2026 07:57
```mjs
import process from 'node:process';

process.addUncaughtExceptionCaptureCallback((err) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't it be available on global process?


This feature is not available in [`Worker`][] threads.

## `process.addUncaughtExceptionCaptureCallback(fn)`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we land it as experimental first?

method with a non-`null` argument while another capture function is set will
throw an error.

Using this function is mutually exclusive with using the deprecated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we should add this to changes: at the top of this function documentation

// Otherwise, the primary callback (if set) is called.
function addUncaughtExceptionCaptureCallback(fn) {
if (typeof fn !== 'function') {
throw new ERR_INVALID_ARG_TYPE('fn', 'Function', fn);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new ERR_INVALID_ARG_TYPE('fn', 'Function', fn);
validateFunction(fn, 'fn');

- Mark addUncaughtExceptionCaptureCallback as experimental
- Add changes entry to setUncaughtExceptionCaptureCallback docs
- Use validateFunction instead of manual type check
@mcollina mcollina force-pushed the repl/remove-domain-dependency branch from 6f50f41 to 6125d23 Compare February 8, 2026 11:47
@mcollina
Copy link
Member Author

mcollina commented Feb 9, 2026

@RafaelGSS ping

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

baking-for-lts PRs that need to wait before landing in a LTS release. dont-land-on-v20.x PRs that should not land on the v20.x-staging branch and should not be released in v20.x. dont-land-on-v22.x PRs that should not land on the v22.x-staging branch and should not be released in v22.x. needs-ci PRs that need a full CI run. needs-citgm PRs that need a CITGM CI run. repl Issues and PRs related to the REPL subsystem. request-ci-failed An error occurred while starting CI via request-ci label, and manual interventon is needed. semver-minor PRs that contain new features and should be released in the next minor version.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants