You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Linux lab178 6.18.19-1.qubes.fc37.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Mar 24 01:20:46 GMT 2026 x86_64 GNU/Linux
Subsystem
module
What steps will reproduce the bug?
In an async resolve hook of a custom loader, await a message from a MessagePort. The message never arrives and the promise never resolves.
This repository has a reproduction. It does a simple pong exchange with the main thread. On v23 and before it works. On v24 and later it hangs forever.
It looks like, the loader architeture for version newer than 24.11.1 does not support this async communication pattern inside resolve() hook.
In newer Node.js versions, resolve() runs through a synchronous request/response flow between the main thread and the loader worker. While resolve() is running, the worker cannot process incoming MessagePort events needed to resolve the promise.
Providing a synchronous loader.mjs resolves this issue @cinderflash
let port
// Store port for talking to main thread
export async function initialize (...args) {
port = args[0]
port.start()
// Listen for PONGs
port.addEventListener('message', event => {
console.log(event.data);
})
}
export async function resolve(specifier, context, next) {
port.postMessage('ping');
return next(specifier, context)
}
Confirming this reproduces, and I narrowed down where it starts. The first bad version is v24.12.0: v24.11.1 works, and v24.12.0 through v26.5.0 all stall, consistently across repeated runs.
It looks like the cause is #60380 ("esm: use sync loading/resolving on non-loader-hook thread"), which landed in 24.12.0. Resolution is now synchronous from the non-loader-hook thread, so the main thread sits in AtomicsWait while resolve() runs and never gets to service the MessagePort exchange the hook is waiting on. The hook waits for a reply only the blocked thread can send, so both sides deadlock.
That also explains why there's no error. The worker only reports never-settle from its beforeExit handler in lib/internal/modules/esm/worker.js, and calling port.start() in the hook keeps a live handle on the worker's event loop, so beforeExit never fires. The process just hangs indefinitely instead of throwing ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED (I left it 40s with no output).
Activity
Sahitya3105 commentedon May 3, 2026
Hi! I’m interested in working on this issue. Could you please assign it to me?
SudhansuBandha commentedon May 14, 2026
It looks like, the loader architeture for version newer than 24.11.1 does not support this async communication pattern inside resolve() hook.
In newer Node.js versions, resolve() runs through a synchronous request/response flow between the main thread and the loader worker. While resolve() is running, the worker cannot process incoming MessagePort events needed to resolve the promise.
Providing a synchronous loader.mjs resolves this issue @cinderflash
fix(guardrails): forward abort telemetry synchronously to avoid a hun…
fix(guardrails): forward abort telemetry synchronously to avoid a hun…
fix(guardrails): forward abort telemetry synchronously to avoid a hun…
fix(guardrails): forward abort telemetry synchronously to avoid a hun…
fix(guardrails): forward abort telemetry synchronously to avoid a hun…
bitpshr commentedon Aug 9, 2026
Confirming this reproduces, and I narrowed down where it starts. The first bad version is v24.12.0: v24.11.1 works, and v24.12.0 through v26.5.0 all stall, consistently across repeated runs.
It looks like the cause is #60380 ("esm: use sync loading/resolving on non-loader-hook thread"), which landed in 24.12.0. Resolution is now synchronous from the non-loader-hook thread, so the main thread sits in
AtomicsWaitwhileresolve()runs and never gets to service theMessagePortexchange the hook is waiting on. The hook waits for a reply only the blocked thread can send, so both sides deadlock.That also explains why there's no error. The worker only reports
never-settlefrom itsbeforeExithandler inlib/internal/modules/esm/worker.js, and callingport.start()in the hook keeps a live handle on the worker's event loop, sobeforeExitnever fires. The process just hangs indefinitely instead of throwingERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED(I left it 40s with no output).Minimal repro:
Happy to open a PR with whichever direction you'd prefer, whether that's documenting the limitation or making the hang surface an error instead.
doc: warn against awaiting replies in module hooks
doc: warn against awaiting replies in module hooks