Skip to content

Runtime: only install the uncaughtException handler for standalone programs#2258

Draft
hhugo wants to merge 1 commit into
masterfrom
fix-1277-uncaught-exception-handler
Draft

Runtime: only install the uncaughtException handler for standalone programs#2258
hhugo wants to merge 1 commit into
masterfrom
fix-1277-uncaught-exception-handler

Conversation

@hhugo

@hhugo hhugo commented May 29, 2026

Copy link
Copy Markdown
Member

Fixes #1277.

Problem

The runtime registers a process-wide uncaughtException handler at module-load time (runtime/js/sys.js):

process.on("uncaughtException", function (err, origin) {
  caml_fatal_uncaught_exception(err);
  process.exit(2);
});

This is meant to emulate native OCaml's fatal-error behaviour (print Fatal error: exception …, exit with code 2). But it runs unconditionally, even when the generated code is loaded as a library. As reported in #1277, requiring jsoo output in the Node REPL then overrides the host's error handling: an error completely unrelated to the OCaml code (e.g. a typo at the prompt) trips the handler and aborts the whole process via process.exit(2) instead of letting the REPL recover.

Fix

Only install the handler when the program is run directly (require.main === module, e.g. node a.js), not when it is loaded via require("./a.js") or in the REPL. module/require are already in scope inside the runtime (the runtime uses require("node:fs") directly), so the check works through the IIFE wrapper. When the discriminator is unavailable (no CommonJS module, e.g. ESM/browser), the previous behaviour is preserved.

This matches the direction suggested on the issue: keep the fatal-error logic for standalone programs only.

The same guard is applied to the wasm_of_ocaml runtime (runtime/wasm/runtime.js).

Behaviour after the fix

  • node a.js — handler installed; an async uncaught exception still prints Fatal error: exception … and exits 2 (unchanged).
  • require("./a.js") (library / REPL) — handler not installed; the host's own error handling applies, so unrelated errors no longer abort the process and the REPL recovers normally.

Tests

Added a regression test in compiler/tests-compiler/error.ml that compiles a module, loads it from a driver via require, and asserts process.listenerCount("uncaughtException") === 0.

@hhugo hhugo marked this pull request as draft May 29, 2026 09:26
@hhugo hhugo force-pushed the fix-1277-uncaught-exception-handler branch from e569040 to 9d5acd2 Compare June 4, 2026 20:56
…ograms

The runtime registered a process-wide "uncaughtException" handler at
module-load time that called process.exit(2) on any uncaught exception.
This emulates native OCaml's fatal-error behaviour, but it ran even when
the generated code was loaded as a library: requiring jsoo output in the
Node REPL would override the host's error handling and abort the whole
process on errors unrelated to the OCaml code.

Only install the handler when the program is run directly (require.main
=== module), not when loaded via require or in the REPL. The same guard
is applied to the wasm_of_ocaml runtime.

Fixes #1277

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@hhugo hhugo force-pushed the fix-1277-uncaught-exception-handler branch from 9d5acd2 to 119284b Compare June 4, 2026 21:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Using js_of_ocaml generated code in node top level alters node's default error handling

1 participant