Repository navigation
Conversation
|
Updated 5:07 PM PT - Sep 6th, 2026
❌ @robobun, your commit 864ac3e has 5 failures in
🧪 To try this PR locally: bunx bun-pr 34193That installs a local version of the PR into your bun-34193 --bun |
WalkthroughChangesWorker error exit codes
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/js/node/worker_threads/worker_threads.test.ts`:
- Around line 574-576: Reorder the subprocess assertions in the worker test
helper around the stdout parsing and exit-code check, including the
corresponding block near the second referenced location: validate or parse
stdout first, then assert exitCode last while preserving the existing stderr
behavior for failures.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 35be9d5b-ffdf-41dc-9cb6-ab23f75ea6eb
📒 Files selected for processing (2)
src/jsc/web_worker.rstest/js/node/worker_threads/worker_threads.test.ts
There was a problem hiding this comment.
LGTM — one-line Node.js compat fix with thorough test coverage.
Checked VirtualMachine::unhandled_rejection in Mode::Bun: it falls through to the hook without setting exit_code, while uncaught_exception sets it at :1436 — confirms the root cause.
Verified process.on('unhandledRejection') short-circuits at handle_unhandled() before the hook, so the new assignment can't clobber the handled case.
Tests spawn subprocesses (isBunTest short-circuit noted), drain pipes concurrently, and mirror the existing run() helper pattern in the same file.
Extended reasoning...
Overview
One-line change in src/jsc/web_worker.rs: on_unhandled_rejection now sets vm.exit_handler.exit_code = 1 at the top, so a worker killed by an unhandled promise rejection reports exit code 1 to the parent (matching Node.js and the existing sync-throw path). Four new subprocess tests in test/js/node/worker_threads/worker_threads.test.ts cover the rejection path, the sync-throw control, the worker's own process.on('exit') argument, and that a registered unhandledRejection handler still suppresses the nonzero exit.
Security risks
None. This is a pure exit-code-reporting change on an existing error path; no auth, crypto, parsing, or user-input handling is touched.
Level of scrutiny
Low-to-moderate. I traced the two entry paths in VirtualMachine.rs:
uncaught_exception(sync throw) setsexit_code = 1at :1436 before callingon_unhandled_rejection— the new line is a redundant re-assignment on that path.unhandled_rejectioninMode::Bun(:3354) callshandle_unhandled(); if a JSunhandledRejectionhandler claims it, it returns early and never reaches the hook (so the 4th test's suppression case is unaffected). If unclaimed, it falls through to :3414 and invokes the hook without settingexit_code— exactly the gap this PR closes.
The unconditional = 1 matches how uncaught_exception already behaves (also unconditional), and dispatch_on_exit runs afterward so process.on('exit') handlers can still mutate process.exitCode — consistent with Node.
Other factors
- Tests are well-designed: spawned (with a comment explaining the
isBunTestshort-circuit),test.concurrent, pipes drained viaPromise.all, and the combined{ exitCode, stderr }assertion mirrors the existing SHARE_ENVrun()helper in the same file. CodeRabbit's assertion-ordering nit was withdrawn as matching file convention. - PR description documents fail-before verification and Node v26 cross-checks for the unchanged paths (clean exit,
process.exit(N), natural finish). - No outstanding unresolved review comments.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/js/node/worker_threads/worker-error-exit-code.test.ts`:
- Around line 21-23: Reorder assertions in the worker subprocess tests so the
parsed stdout result is validated before stderr and exitCode. Update the helper
around run() to parse and assert the expected worker result first, passing the
expected result into run() if needed; apply the same ordering to the direct
subprocess test at test/js/node/worker_threads/worker-error-exit-code.test.ts
lines 21-23 and 61-63.
- Around line 66-72: Update the unhandledRejection test’s worker script so its
handler closes or unreferences parentPort instead of calling process.exit(0).
Let the worker terminate naturally, while preserving the expected { err: null,
code: 0 } result and the existing rejection-handling scenario.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: bae1ec07-26b5-4ed4-a966-85fbe5684c1c
📒 Files selected for processing (1)
test/js/node/worker_threads/worker-error-exit-code.test.ts
There was a problem hiding this comment.
LGTM — one-line Node compat fix with solid coverage.
Checked VirtualMachine::unhandled_rejection (Mode::Bun falls through to the hook without setting exit_code) vs uncaught_exception (sets it at :1436 before the hook) — the asymmetry is real and this closes it.
Verified the process.on('unhandledRejection') suppression path returns before the hook is reached, so the new assignment can't leak into the handled case.
The assignment precedes dispatch_on_exit, so user process.on('exit') handlers still see and can override the code — same ordering as the sync path.
Tests cover rejection, sync-throw control, worker-side process.on('exit') arg, and handler suppression; CodeRabbit feedback (combined-object assertion, parentPort.close() over process.exit(0)) was applied.
Extended reasoning...
Overview
One production line in src/jsc/web_worker.rs: vm.exit_handler.exit_code = 1 at the top of the worker's on_unhandled_rejection hook, plus a 3-line comment explaining why. A new 80-line test file covers four cases (rejection → exit 1, sync throw → exit 1 as control, worker's own process.on('exit') receives 1, and unhandledRejection handler suppresses to 0).
Mechanism verified
I read both entry paths in VirtualMachine.rs:
uncaught_exception(sync throw): setsexit_handler.exit_code = 1at :1436, then calls the hook at :1437.unhandled_rejectioninMode::Bun: ifBun__handleUnhandledRejectionreturns handled, returns early (so the suppression test is sound — the hook is never reached andexit_codestays 0); otherwise falls through to :3414 and calls the hook without settingexit_code. That is exactly the gap this line closes.
Setting it inside the hook is idempotent for the sync path (already 1) and fixes the async path. It runs before ExitHandler::dispatch_on_exit, so the worker's own process.on('exit') handlers see 1 and can still mutate process.exitCode — matching the existing comment at that call site ("code 1; they may change process.exitCode").
Security risks
None. No parsing, no untrusted input, no allocation, no new control flow — a single field write on an already-owned struct.
Level of scrutiny
Medium file (worker lifecycle in web_worker.rs is delicate), but the change itself is trivially low-risk: an unconditional u8 assignment on a path that already runs and already terminates the worker. It only makes the async-rejection path consistent with the sync-throw path, which has done the same assignment for a long time.
Other factors
- All CodeRabbit threads are resolved: the combined
{stdout, stderr, exitCode}assertion pattern was adopted, and the suppression test now usesparentPort.close()so it exits naturally rather than viaprocess.exit(0). - Tests are subprocess-spawned (comment explains
isBunTestshort-circuit), usebunExe()/bunEnv, drain pipes concurrently, and follow the directory's standalone-file convention. - PR description documents fail-before verification and Node v26 parity checks for the unchanged paths.
- No prior claude[bot] review on this PR.
There was a problem hiding this comment.
Additional findings (outside current diff — PR may have been updated during review):
-
🔴
src/runtime/napi/napi_body.rs:969-971— Commit 26716d3 ("ci: retrigger") looks like a botched push: it reverted the actual fix (vm.exit_handler.exit_code = 1inon_unhandled_rejectionatsrc/jsc/web_worker.rs) and instead introduced unrelated N-API null-argument guards acrossnapi_body.rsandnapi.cpp. The net PR diff now has zero changes toweb_worker.rs, so the two new rejection tests (worker-error-exit-code.test.ts:30 and :44) will fail withcode: 0. Please restore theweb_worker.rschange and drop the N-API hunks (or split them into their own PR).Extended reasoning...
What happened
The PR history shows the fix was landed and then silently undone:
- c78eff6 added the fix:
vm.exit_handler.exit_code = 1;at the top ofon_unhandled_rejectioninsrc/jsc/web_worker.rs. - 26716d3 ("ci: retrigger") removed exactly those 4 lines from
web_worker.rsand simultaneously added N-API null-argument guards tosrc/jsc/bindings/napi.cppandsrc/runtime/napi/napi_body.rs.
git diff 1e351180..HEAD --stat(net diff vs merge base) confirms:src/jsc/bindings/napi.cpp | 2 + src/runtime/napi/napi_body.rs | 24 +++++++ .../worker_threads/worker-error-exit-code.test.ts | 80 ++++++++++++++++++++++There is no
web_worker.rsin the changed-files list. The currenton_unhandled_rejectionatsrc/jsc/web_worker.rs:1461-1467does not setexit_code.Why the tests will fail
Step through the "unhandled promise rejection exits 1" test at line 30:
- The worker script does
Promise.reject(new Error("task-reject"))inside the message handler. VirtualMachine::unhandled_rejectionfires. InMode::Bunit calls the worker'son_unhandled_rejectionhook without touchingexit_handler.exit_code(this is the exact asymmetry the PR description identifies as the bug).- Because 26716d3 removed the
vm.exit_handler.exit_code = 1line, the hook still doesn't set it either. - The hook dispatches
'error', runsprocess.on('exit'), and arms termination;shutdown()reads the untouchedexit_code(0) and posts it to the parent. - Parent prints
{"err":"Error: task-reject","code":0}. - The test asserts
stdout === JSON.stringify({ err: "Error: task-reject", code: 1 })→ fails.
The same applies to the test at line 44, which asserts
{ workerExitArg: 1, code: 1 }— both values will be 0. The PR description itself says "Fail-before verified by stashingsrc/and rebuilding: the two rejection tests fail withcode: 0" — that's exactly the state this branch is now in.The unrelated N-API changes
The same commit added
is_empty()/NAPI_CHECK_ARGguards tonapi_is_array,napi_get_array_length,napi_strict_equals,napi_is_error,napi_is_arraybuffer,napi_is_dataview,napi_get_dataview_info,napi_is_date,napi_detach_arraybuffer, andnode_api_create_buffer_from_arraybuffer. These are entirely unrelated to worker_threads exit codes, aren't mentioned anywhere in the PR title/description/review, and have no accompanying tests. They look like they belong on a different branch and got mixed in during a bad rebase or force-push. Per the repo guidelines ("don't ride file-wide standardization on a focused bugfix"), they should be dropped from this PR regardless.Fix
Revert commit 26716d3 in its entirety — that both restores the
web_worker.rsfix and removes the stray N-API hunks. If the N-API guards are wanted, open a separate PR for them with tests. - c78eff6 added the fix:
There was a problem hiding this comment.
LGTM — one-line fix aligning worker unhandled-rejection exit code with Node.
What was reviewed:
- Verified
VirtualMachine::uncaught_exceptionsetsexit_code = 1before invoking the hook (VirtualMachine.rs:1436) andunhandled_rejectioninMode::Bundoes not — the PR's cause analysis is accurate. - Confirmed the hook is only reached when no
process.on('unhandledRejection')handler claimed the rejection (handle_unhandled()returns early first), so the new assignment can't fire in the suppressed case. - Checked that a user-set
process.exitCodebeing overwritten to 1 here matches the existing sync-throw path behavior, and thatdispatch_on_exitstill lets exit handlers change it afterward. - Tests follow repo subprocess conventions (combined-object assertion,
describe.concurrent, stderr masked on success per ASAN guidance); all CodeRabbit threads resolved.
Extended reasoning...
Overview
The PR touches two files: a 1-line assignment (+3 comment lines) in src/jsc/web_worker.rs's on_unhandled_rejection hook, and a new 80-line test file at test/js/node/worker_threads/worker-error-exit-code.test.ts. The fix sets vm.exit_handler.exit_code = 1 at the top of the worker's unhandled-rejection hook so a Worker killed by an unhandled promise rejection reports exit code 1 to the parent (matching Node.js), instead of 0.
Security risks
None. This changes the value of an integer exit code reported through an existing event; no new inputs, no parsing, no auth/crypto/permissions surface.
Level of scrutiny
Low-to-moderate. The Rust change is a single scalar assignment on a path that already existed and already runs user callbacks; it introduces no new control flow, allocations, or FFI calls. I traced both entry paths to the hook: uncaught_exception (VirtualMachine.rs:1436) already sets exit_code = 1 immediately before invoking it, so this line is redundant-but-harmless there; unhandled_rejection in Mode::Bun (VirtualMachine.rs:3354) calls handle_unhandled() first and returns early if a JS unhandledRejection handler claimed it, so the hook (and thus this assignment) only runs when the rejection is truly unhandled — the suppression test correctly exercises the early-return path, not this line. The one edge case (a user-set process.exitCode being clobbered to 1) already happens on the sync-throw path and matches Node; dispatch_on_exit runs afterward so exit handlers can still override.
Other factors
Test coverage is thorough for the change: the rejection path (the actual fix), a sync-throw control, the worker's own process.on('exit') argument, and suppression via an unhandledRejection handler. Tests are spawned subprocesses (necessary because isBunTest short-circuits the in-process rejection path), use the combined {stdout, stderr, exitCode} assertion convention, and run under describe.concurrent. All CodeRabbit review threads are resolved — the author addressed the "don't force process.exit(0) in the suppression test" concern by switching to parentPort.close() so the worker exits naturally. The commit history shows an accidental N-API hunk was reverted in da2c1de; the final diff is clean. No CODEOWNERS entries cover these paths. No prior claude[bot] reviews on this PR.
|
CI on da2c1de: the new
Diff is the one-line |
|
Two notes from re-deriving this on current main (b9ef885), where it still reproduces (
A rebased version of both with tests for the |
|
One case this PR leaves different from Node: a rejection while the worker entry is still loading (a top-level // worker.js
process.on("exit", code => { console.log("listener", code); process.exitCode = 42; });
Promise.reject(new Error("boom"));
// parent: worker.on("exit", c => console.log("exit", c))
// node v26.3.0: listener 1, exit 42
// this PR: listener 1, exit 1A rejection from a later turn already keeps the listener's value, and the Err(_) => {
// process.exit() may have run during load; don't clobber its code. Nor a
// nonzero one: an uncaught error during load sets 1 and then runs the
// 'exit' listeners, which may change process.exitCode.
if !self.exit_called.load(Ordering::Relaxed) && vm.exit_handler.exit_code == 0 {
vm.as_mut().exit_handler.exit_code = 1;
}The |
…ction
A node:worker_threads Worker killed by an unhandled promise rejection
reported exit code 0 to the parent's 'exit' event and to its own
process.on('exit') listeners. Node reports 1. A synchronous throw
already exits 1.
The sync-throw path goes through VirtualMachine::uncaught_exception,
which sets exit_handler.exit_code = 1 before invoking the worker's
on_unhandled_rejection hook. A rejection reported from an event-loop
turn goes through VirtualMachine::unhandled_rejection, which in the
default mode invokes the same hook with the code still 0. The hook
dispatches 'error', runs the worker's 'exit' listeners and requests the
stop, so shutdown() posted 0 to the parent.
Set exit_code = 1 in the hook, before the 'exit' listeners run, so both
paths report the same code.
da2c1de to
535ab29
Compare
There was a problem hiding this comment.
LGTM — one-line fix that makes the worker's unhandled-rejection hook write exit_code = 1 before dispatching the worker's own process.on('exit'), matching the sync-throw path and Node.
What was reviewed:
- Traced both entry paths into
on_unhandled_rejection:uncaught_exceptionalready writesexit_code = 1(redundant but harmless), andunhandled_rejectioninMode::Bunfalls through without writing it — the gap this closes. - Placement is after the
script_allowed()guard, soterminate()still wins and reports its own code; and beforedispatch_on_exit, so the worker's exit listeners see 1 (the pre-existing comment there already claimed "code 1"). - Checked the
unhandledRejection-handler suppression test:handle_unhandled()returns early before the hook runs, soexit_codeis never touched — the test's expectation of code 0 is sound. - Tests spawn subprocesses (correct —
isBunTestshort-circuits the in-process rejection path), drain pipes concurrently, and assert combined{stdout, stderr, exitCode}.
Extended reasoning...
Overview
The PR adds a single assignment vm.exit_handler.exit_code = 1 inside on_unhandled_rejection in src/jsc/web_worker.rs, plus a new test file with six subprocess-spawned cases covering unhandled rejections and synchronous throws from both parentPort message handlers and timer callbacks, the worker's own process.on('exit') receiving code 1, and suppression via a process.on('unhandledRejection') listener. The branch was force-pushed on 2026-09-06 with two fresh commits (the fix, then a comment shortening in response to a bot inline), superseding whatever the 2026-07-15 reviews saw.
Security risks
None. This changes only the exit code reported when a worker thread dies from an unhandled rejection. No untrusted input parsing, no auth, no crypto, no memory management changes.
Level of scrutiny
Low. The Rust change is one line whose correctness is verifiable by tracing the two callers of the hook in VirtualMachine.rs: uncaught_exception (line ~1712) already sets exit_code = 1 before invoking the hook, so the new assignment is a no-op there; unhandled_rejection in the default Mode::Bun arm falls through to the hook without setting it, which is exactly the reported divergence from Node. Placement after the !vm.script_allowed() early return means a prior terminate() keeps its own exit code, and placement before ExitHandler::dispatch_on_exit(vm) means the worker's process.on('exit') listeners observe 1 and can still override via process.exitCode — both confirmed against the surrounding code, and the pre-existing comment at the dispatch_on_exit call already documented "code 1".
Other factors
The test file follows the harness conventions the review guide calls out: bunExe()/bunEnv, describe.concurrent for independent subprocess tests, concurrent draining of stdout/stderr/exited, combined-object assertion, and -e for single-file fixtures. The isBunTest short-circuit at VirtualMachine.rs:3779 is real and justifies spawning rather than testing in-process. Fail-before evidence in the PR description shows the rejection tests fail with code: 0 on a build without the fix. The changed paths are not covered by .github/CODEOWNERS. No outstanding CHANGES_REQUESTED reviews; all third-party inline threads are resolved by a non-author, and the github-actions inline on line 1180 was addressed by the follow-up "shorten the comment" commit.
|
Status after the rebase onto main (head 864ac3e):
The diff is the single |
… await pending; fold in the cases from #37981, #34193 and #33356 A worker whose 'beforeExit' listener calls process.exit(0) while the entry module's top-level await is still pending exited 13. The exit-13 stamp now also checks that no stop was requested, as the enclosing 'beforeExit' condition does. Node exits 0 here. Tests carried over from the PRs this one supersedes: - #37981: repl -e last-timer rejection, two main-thread 'beforeExit' orderings, the top-level-await worker cases, and the reported-before-queued-task ordering. - #34193: worker-error-exit-code.test.ts (message-listener and timer sites, the worker's own 'exit' argument, listener suppression). - #33356: a main-thread listener that recovers from a 'beforeExit' rejection after a microtask hop.
|
Closing in favor of #42032. It sets exit code 1 for an unhandled rejection in |
… await pending; fold in the cases from #37981, #34193 and #33356 A worker whose 'beforeExit' listener calls process.exit(0) while the entry module's top-level await is still pending exited 13. The exit-13 stamp now also checks that no stop was requested, as the enclosing 'beforeExit' condition does. Node exits 0 here. Tests carried over from the PRs this one supersedes: - #37981: repl -e last-timer rejection, two main-thread 'beforeExit' orderings, the top-level-await worker cases, and the reported-before-queued-task ordering. - #34193: worker-error-exit-code.test.ts (message-listener and timer sites, the worker's own 'exit' argument, listener suppression). - #33356: a main-thread listener that recovers from a 'beforeExit' rejection after a microtask hop.
… await pending; fold in the cases from #37981, #34193 and #33356 A worker whose 'beforeExit' listener calls process.exit(0) while the entry module's top-level await is still pending exited 13. The exit-13 stamp now also checks that no stop was requested, as the enclosing 'beforeExit' condition does. Node exits 0 here. Tests carried over from the PRs this one supersedes: - #37981: repl -e last-timer rejection, two main-thread 'beforeExit' orderings, the top-level-await worker cases, and the reported-before-queued-task ordering. - #34193: worker-error-exit-code.test.ts (message-listener and timer sites, the worker's own 'exit' argument, listener suppression). - #33356: a main-thread listener that recovers from a 'beforeExit' rejection after a microtask hop.
… await pending; fold in the cases from #37981, #34193 and #33356 A worker whose 'beforeExit' listener calls process.exit(0) while the entry module's top-level await is still pending exited 13. The exit-13 stamp now also checks that no stop was requested, as the enclosing 'beforeExit' condition does. Node exits 0 here. Tests carried over from the PRs this one supersedes: - #37981: repl -e last-timer rejection, two main-thread 'beforeExit' orderings, the top-level-await worker cases, and the reported-before-queued-task ordering. - #34193: worker-error-exit-code.test.ts (message-listener and timer sites, the worker's own 'exit' argument, listener suppression). - #33356: a main-thread listener that recovers from a 'beforeExit' rejection after a microtask hop.
… await pending; fold in the cases from #37981, #34193 and #33356 A worker whose 'beforeExit' listener calls process.exit(0) while the entry module's top-level await is still pending exited 13. The exit-13 stamp now also checks that no stop was requested, as the enclosing 'beforeExit' condition does. Node exits 0 here. Tests carried over from the PRs this one supersedes: - #37981: repl -e last-timer rejection, two main-thread 'beforeExit' orderings, the top-level-await worker cases, and the reported-before-queued-task ordering. - #34193: worker-error-exit-code.test.ts (message-listener and timer sites, the worker's own 'exit' argument, listener suppression). - #33356: a main-thread listener that recovers from a 'beforeExit' rejection after a microtask hop.
Problem
node:worker_threadsWorker that dies from an unhandled promise rejection in an event-loop turn (aparentPort'message'listener, a timer callback) reportsexitcode 0 to the parent, and its ownprocess.on('exit')listeners receive 0. Node reports 1. The'error'event is correct. A synchronous throw from the same places already exits 1.on_unhandled_rejection(src/jsc/web_worker.rs:1163) never sets the exit code. The throw path reaches it throughVirtualMachine::uncaught_exception, which setsexit_handler.exit_code = 1first (src/jsc/VirtualMachine.rs). The rejection path reaches it throughVirtualMachine::unhandled_rejection, which in the default mode does not. The hook then runs the worker's'exit'listeners andshutdown()posts the untouched 0.Fix
exit_handler.exit_code = 1inon_unhandled_rejection, after the "stop already requested" early return and before the worker's'exit'listeners run. Both entry paths now report the same code, and an'exit'listener can still change it, as in Node.process.on('unhandledRejection')listener claims never reaches the hook (Bun__handleUnhandledRejectionreturns first), so that case keeps exit code 0.test/js/node/worker_threads/worker-error-exit-code.test.ts(6 cases: rejection and throw from a message listener and from a timer callback, the worker's own'exit'argument, and suppression by an'unhandledRejection'listener). Without the fix the three rejection cases fail withcode: 0.Background
on_unhandled_rejection: it posts'error'to the parent, runs the worker'sprocess.on('exit')listeners throughExitHandler::dispatch_on_exit, and requests the stop.spin()then callsshutdown(), which readsexit_handler.exit_codeand reports it to the parent as the'exit'code.VirtualMachine::unhandled_rejectionis what JSC's rejected-promise list drains into at the end of an event-loop turn.VirtualMachine::uncaught_exceptionis the path for a thrown exception (and for the entry module's own rejection).Notes
1.4.3-canary.1+f42e98025(one day after 1.4.2) against Node v26.3.0: the message-listener rejection still exits 0 (5/5 runs of the original repro), the timer-callback rejection with an interval keeping the loop alive exits 0. Both go through this hook. Both exit 1 with this change.setTimeout(() => Promise.reject(e), 20)with nothing else alive) is never reported at all (no'error', the worker's'unhandledRejection'listeners do not run, exit 0). The worker loop does not walk the rejected-promise list after its last turn. event loop: report promise rejections left by the turn that let the loop go idle #37981 fixes that; its worker test assertscodeisnot.toBe(13)rather than1because of the bug fixed here, so the two changes are complementary.f42e98025(src/jsc/web_worker.rsis identical betweenf42e98025and this PR's base): 3 of 6 tests fail withcode: 0. The passing run with the fix was done on the earlier base of this PR; the change is the same single assignment.test/js/web/workers/worker.test.tsandtest/js/node/worker_threads/worker-top-level-await.test.tson the earlier base.