Skip to content

CLI process lifecycle: output before an uncaught error is lost, process.exit(n) is reported as an uncaught error, process.exitCode / 'exit' are ignored, async errors exit 0 #48

Closed

Description

@hassidan

Test I ran

Eight two- or three-line scripts, each run with node <file> and slice run <file>, recording exit code, stdout and the first stderr line.

the eight scripts

a_uncaught_after_output.ts

console.log("line 1");
console.error("line 2 (stderr)");
throw new Error("boom");

b_exit_0.ts

console.log("before exit");
process.exit(0);

c_exit_3.ts

console.log("before exit");
process.exit(3);

d_exit_5_in_timer.ts

console.log("a");
setTimeout(() => { console.log("b"); process.exit(5); }, 5);

e_exitCode_and_exit_event.ts

process.on("exit", (code) => console.log("exit event, code=" + code));
process.exitCode = 7;
console.log("main done");

f_unhandled_rejection.ts

Promise.reject(new Error("unhandled rejection"));
console.log("main done");

g_throw_in_timer.ts

console.log("main done");
setTimeout(() => { throw new Error("thrown in timer"); }, 5);

h_throw_in_async.ts

(async () => { await null; throw new Error("thrown in async fn"); })();
console.log("main done");

Result

script                             | node v24.22.2: exit / stdout / stderr        | slice v0.10.7: exit / stdout / stderr
a_uncaught_after_output.ts         | 1 / line 1| / line 2 (stderr)                | 1 /  / Error: Uncaught Error: boom (unknown at :11:6)
b_exit_0.ts                        | 0 / before exit| /                           | 1 /  / Error: Uncaught Error: process.exit(0)
c_exit_3.ts                        | 3 / before exit| /                           | 1 /  / Error: Uncaught Error: process.exit(3)
d_exit_5_in_timer.ts               | 5 / a|b| /                                   | 0 / a|b| / 
e_exitCode_and_exit_event.ts       | 7 / main done|exit event, code=7| /          | 0 / main done| / 
f_unhandled_rejection.ts           | 1 / main done| / Error: unhandled rejection  | 0 / main done| / 
g_throw_in_timer.ts                | 1 / main done| / Error: thrown in timer      | 0 / main done| / 
h_throw_in_async.ts                | 1 / main done| / Error: thrown in async fn   | 0 / main done| /

(| marks a newline in stdout.) The .cas and slice compile standalone forms of scripts a and b behave the same as slice run.

What is wrong

# Behaviour on Slice Expected
a Everything written to stdout and stderr before an uncaught exception is discarded Output written before the throw is kept
b, c Top-level process.exit(0) / process.exit(3) exit with code 1, print Error: Uncaught Error: process.exit(N) and drop prior stdout Exit with code N, keep stdout, print nothing
d process.exit(5) inside a timer callback exits with code 0 Exit code 5
e process.exitCode = 7 is ignored (exit 0) and the 'exit' listener never runs Exit code 7, listener runs
f, h An unhandled promise rejection / an error thrown in an async function exits 0 with no message Non-zero exit and the error on stderr
g An error thrown inside a setTimeout callback exits 0 with no message Non-zero exit and the error on stderr

Rows f–h mean a failing script can report success to a shell, CI job or child_process caller. docs/general/COMPATIBILITY.md lists process.exitCode, process.exit(code) and process.on as part of the supported process surface.

Environment

  • Slice v0.10.7, commit ea7c70c, built with cargo +1.96.0 build --release --locked --bin slice
  • Reference: Node.js v24.22.2 (same script, same machine)
  • Ubuntu 26.04, Linux 7.0, x86_64
  • Scripts a, b, e and f give the same result on the v0.10.5 tag, so this is not a regression in 0.10.6/0.10.7.

Activity

  1. siktec-lab commented on Nov 11, 2026

    @siktec-lab
    Collaborator

    Fixed in PR #65 (v0.10.9):

    • Implemented an uncatchable native __slice_process_exit(code) termination unwind (PanicError) so process.exit(code) inside try / catch, Promises, and microtasks halts JS execution immediately and runs process.on("exit") listeners once.
    • Wired HostHooks::promise_rejection_tracker to track unhandled Promise rejections and either invoke process.on("unhandledRejection") or exit with code 1 (Uncaught (in promise)).
    • Added full process.exitCode, process.on("beforeExit"), and process.on("uncaughtException") / uncaughtExceptionMonitor support gated behind a zero-overhead DIRTY_PROCESS bitmask.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions