Summary
#102 gave top-level-await rejections proper "Uncaught (in promise)"
reporting (vm.unhandledRejectionPrefix / handleUncaughtException). That
mechanism is specific to the TLA path. A plain async function call at
script top level, whose returned promise ends up rejected with zero
.then()/.catch() handlers ever attached, gets no report at all and the
process exits 0 - unlike real Node/V8, which prints the rejection and (as of
modern Node defaults) exits non-zero.
Repro
async function f() { throw new Error("x"); }
f();
console.log("script end");
Real Node:
script end
file:///.../repro.mjs:1
async function f() { throw new Error("x"); }
^
Error: x
at f (...)
...
Node.js v26.3.0
exit code 1.
Current paserati/noderati:
exit code 0 - the rejection is silently dropped.
Scope note
This is a distinct feature gap, not a regression of #118 (which fixed a
different bug: an awaited rejection silently hanging its caller's own
promise forever with no error surfacing anywhere, even internally). Here,
after #118's fix, the promise genuinely does settle as rejected correctly -
there's just nothing watching for "a promise settled rejected and, by the
time the microtask queue goes idle, still has zero reject handlers attached"
the way Node's unhandledRejection/rejectionHandled event pair does.
Where to look
- No existing tracking of "settled-rejected, no handler ever attached"
promises found in pkg/vm/promise.go (rejectPromise/
triggerPromiseReactions) - reactions are read from
RejectReactions/FulfillReactions at settle time or attach time, but
nothing records "this promise settled with an empty reaction list" for a
later check once the event loop is idle.
- Node's spec-adjacent behavior: a rejection is "unhandled" if, by the time
the job queue empties, no .then()/.catch() was ever attached (checked
via a microtask-queued callback after .then(), not synchronously - a
handler attached after rejection, on the same tick chain, still counts
as handled). This needs a similar deferred check, likely at whatever
point paserati's own event-loop driver decides "nothing left to run."
Context
Found while verifying #118's fix (pi --help under noderati) - confirmed
via the repro above, both against a go run ./cmd/paserati build and
through noderati. Not blocking any current work; noted for whenever runtime
event-loop fidelity is revisited.
Summary
#102gave top-level-await rejections proper "Uncaught (in promise)"reporting (
vm.unhandledRejectionPrefix/handleUncaughtException). Thatmechanism is specific to the TLA path. A plain async function call at
script top level, whose returned promise ends up rejected with zero
.then()/.catch()handlers ever attached, gets no report at all and theprocess exits 0 - unlike real Node/V8, which prints the rejection and (as of
modern Node defaults) exits non-zero.
Repro
Real Node:
exit code 1.
Current paserati/noderati:
exit code 0 - the rejection is silently dropped.
Scope note
This is a distinct feature gap, not a regression of #118 (which fixed a
different bug: an awaited rejection silently hanging its caller's own
promise forever with no error surfacing anywhere, even internally). Here,
after #118's fix, the promise genuinely does settle as rejected correctly -
there's just nothing watching for "a promise settled rejected and, by the
time the microtask queue goes idle, still has zero reject handlers attached"
the way Node's
unhandledRejection/rejectionHandledevent pair does.Where to look
promises found in
pkg/vm/promise.go(rejectPromise/triggerPromiseReactions) - reactions are read fromRejectReactions/FulfillReactionsat settle time or attach time, butnothing records "this promise settled with an empty reaction list" for a
later check once the event loop is idle.
the job queue empties, no
.then()/.catch()was ever attached (checkedvia a microtask-queued callback after
.then(), not synchronously - ahandler attached after rejection, on the same tick chain, still counts
as handled). This needs a similar deferred check, likely at whatever
point paserati's own event-loop driver decides "nothing left to run."
Context
Found while verifying #118's fix (
pi --helpunder noderati) - confirmedvia the repro above, both against a
go run ./cmd/paseratibuild andthrough noderati. Not blocking any current work; noted for whenever runtime
event-loop fidelity is revisited.