Start the inspector at runtime on SIGUSR1 / process._debugProcess - #37336
Start the inspector at runtime on SIGUSR1 / process._debugProcess#37336robobun wants to merge 9 commits into
Conversation
Sending SIGUSR1 to a running bun process (or calling process._debugProcess(pid),
which also works on Windows) starts the inspector, matching Node.js. This works
even when the JS thread is stuck in a loop that never returns to the event loop.
Mechanism:
SIGUSR1 handler sem_post only (async-signal-safe)
SignalInspector thread sets a flag, fires VM::notifyNeedDebuggerBreak on the
main VM, wakes the event loop
JSC services the trap at the next safe point in any JIT
tier (SignalSender patches DFG/FTL invalidation
points) and calls the per-VM callback added in
oven-sh/WebKit#287
callback (JS thread) starts the inspector if requested, drains queued CDP
messages, and enters Debugger::breakProgram() when a
Debugger.pause was dispatched
CDP delivery after activation reuses the same trap, so the debugger thread
never blocks on the target. An idle target is handled by the event-loop wakeup
instead. Platforms where JSC's GC owns SIGUSR1 (FreeBSD) leave the signal alone.
Also: --inspect-port and --disable-sigusr1 flags; a user SIGUSR1 listener takes
over the signal and hands it back when removed; inspector.open() and this path
now share Debugger::start_at_runtime.
Replaces #26867 and #34106. Requires oven-sh/WebKit#287.
To be replaced with the main autobuild once #287 lands.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (4)
WalkthroughThe PR adds runtime inspector activation through ChangesRuntime inspector
WebKit preview dependency
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 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 `@scripts/build/deps/webkit.ts`:
- Line 6: Keep WEBKIT_VERSION pinned to the PR `#287` preview while that PR
remains open; once it merges or closes, replace the preview identifier with the
resulting merged WebKit commit SHA so prebuilt downloads continue using an
available release.
In `@src/jsc/bindings/BunProcess.cpp`:
- Around line 4680-4685: Update the _debugProcess argument handling around pid
to validate the original numeric value is within the signed int32 range before
calling toInt32. Reject values outside that range, including values that would
wrap during conversion, while preserving the existing positive-integer
validation and exception handling.
In `@src/jsc/RuntimeInspector.rs`:
- Around line 333-341: The Windows debug-handler mapping name is duplicated
across Rust and C++; define the format once near the Rust mapping-name
construction and expose it through an extern "C" accessor or shared constant,
then update BunProcess.cpp at lines 4716-4717 to use that shared definition
instead of the hardcoded L"bun-debug-handler-%d" literal. Ensure both producer
and consumer generate the identical per-process mapping name.
In `@src/runtime/jsc_hooks.rs`:
- Around line 579-581: Validate the inspect-port option during CLI parsing in
Arguments.rs before storing it as raw bytes, requiring a numeric value within
the supported port range. Reject invalid values with a parse error that includes
the original rejected value, while preserving valid values for the
runtime_inspector activation path.
In `@test/js/bun/runtime-inspector/helpers.ts`:
- Around line 130-149: Update cdpClient to track both promise resolution and
rejection for each pending request, and reject when an incoming CDP response
contains msg.error instead of resolving it. Preserve normal response resolution
and event handling, while ensuring protocol error details propagate to callers
through the returned send promise.
🪄 Autofix
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: 58c1279d-affb-45b0-9d2d-3eadb618af69
📒 Files selected for processing (22)
docs/runtime/debugger.mdxdocs/snippets/cli/run.mdxscripts/build/deps/webkit.tssrc/jsc/Debugger.rssrc/jsc/RuntimeInspector.rssrc/jsc/VM.rssrc/jsc/VirtualMachine.rssrc/jsc/bindings/BunDebugger.cppsrc/jsc/bindings/BunProcess.cppsrc/jsc/bindings/vm/Semaphore.cppsrc/jsc/event_loop.rssrc/jsc/lib.rssrc/options_types/context.rssrc/runtime/cli/Arguments.rssrc/runtime/cli/repl_command.rssrc/runtime/cli/run_command.rssrc/runtime/cli/test_command.rssrc/runtime/jsc_hooks.rstest/js/bun/runtime-inspector/helpers.tstest/js/bun/runtime-inspector/runtime-inspector-posix.test.tstest/js/bun/runtime-inspector/runtime-inspector.test.tstest/js/node/process/process.test.js
💤 Files with no reviewable changes (1)
- test/js/node/process/process.test.js
- Record the SIGUSR1 disposition chosen at startup (runtime_inspector::Sigusr1) and re-apply that, rather than only the activation handler, when the last user SIGUSR1 listener is removed. Under --inspect* the signal now stays ignored after a listener add/remove cycle instead of falling back to the default action. --disable-sigusr1 leaves the inherited disposition alone, as before this change. - process._debugProcess validates the pid the same way process.kill does, so values toInt32 would wrap (2 ** 32 + 1 -> 1) and fractions are rejected instead of signalling an unrelated process. - Test helper cdpClient rejects on CDP error responses; the user-listener test accumulates stdout across signals; the Debugger.pause test runs on ASAN builds too (passes 12/12 locally on the debug ASAN build). - --inspect-port documents the [host:]port form it shares with --inspect. - Shorter comments throughout.
… inspectee On the ASAN lanes the runner sets BUN_JSC_validateExceptionChecks, and a process answering Runtime.evaluate (or pausing) aborts inside JSC's InjectedScript on the unchecked getOwnNonIndexPropertyNames scope in the prebuilt WebKit, the same gap that keeps test/cli/inspect/inspect.test.ts in test/no-validate-exceptions.txt. Every CDP round trip in these files timed out there as a result. Strip the flag for the inspected processes only, as test/js/node/inspector/inspector.test.ts does; the test process, the signalling children and the error-path children still run with it. spawnTarget now kills the child if it fails before handing it to the caller.
…orting ready The two user-listener targets printed their pid before calling process.on, so a signal sent right after spawnTarget returned could still reach the activation handler. debugProcess drains stdout as well, and the banner helpers type the stderr pipe rather than stdout.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/jsc/RuntimeInspector.rs (1)
229-235: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winHandle
sigemptysetandsigactionfailures.Line 234 discards the result of
sigaction. If installation fails, SIGUSR1 can retain its prior disposition whileconfigurerecordsStartInspectororIgnore. Return failure fromplatform::apply, retain the previous disposition on failure, and report the syscall error.🤖 Prompt for 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. In `@src/jsc/RuntimeInspector.rs` around lines 229 - 235, Update the SIGUSR1 setup in platform::apply to check the return values from sigemptyset and sigaction, preserving the previous signal disposition when either syscall fails. Propagate failure from platform::apply and report the corresponding OS error instead of recording StartInspector or Ignore as successfully applied.Source: Coding guidelines
src/jsc/Debugger.rs (1)
605-642: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winRestore debugger startup state after thread-spawn failure.
If
Debugger::createfails, Line 628 clears onlyvm.debugger.Debugger::createhas already setHAS_CREATED_DEBUGGERandhas_started_debugger. Later SIGUSR1,process._debugProcess, andinspector.open()attempts then failcan_start_at_runtime()permanently.Roll back these flags in
Debugger::createbefore returning the spawn error. Preserve a retryable state.🤖 Prompt for 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. In `@src/jsc/Debugger.rs` around lines 605 - 642, Update Debugger::create failure handling in start_at_runtime so a thread-spawn error rolls back all startup state, including HAS_CREATED_DEBUGGER and has_started_debugger, before returning the error. Keep vm.debugger cleared and preserve can_start_at_runtime() so later SIGUSR1, process._debugProcess, or inspector.open() attempts can retry successfully.Source: Coding guidelines
test/js/bun/runtime-inspector/helpers.ts (1)
33-42: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winCancel the pending stream read on timeout.
When
timeoutwinsPromise.race,reader.read()remains pending.waitForBannerthen callsreader.releaseLock()at line 72. That call can throw and replace the useful timeout error.Cancel and await the pending read path before returning the timeout error.
As per coding guidelines, every timeout path must complete the operation and cancel protocols.
🤖 Prompt for 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. In `@test/js/bun/runtime-inspector/helpers.ts` around lines 33 - 42, Update the waitForBanner read loop to track when timeout wins Promise.race, cancel the pending reader.read() operation, and await its completion before propagating the timeout error. Ensure reader.releaseLock() runs only after the cancellation protocol completes, while preserving normal stream completion behavior.Source: Coding guidelines
🤖 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 `@docs/snippets/cli/run.mdx`:
- Around line 141-143: Update the --inspect-port ParamField description to
document URL-prefix input alongside the existing [host:]port form, including an
example such as localhost:4000/prefix. Preserve the current default-port and
free-port details.
In `@test/js/bun/runtime-inspector/helpers.ts`:
- Around line 126-131: Update the subprocess assertion around Promise.all to
report whether stderr is empty using a strict stderr === "" check, replacing the
weaker stderr.includes("error:") condition while preserving the existing stdout
and exitCode assertions.
In `@test/js/bun/runtime-inspector/runtime-inspector.test.ts`:
- Around line 126-145: Update the test around the Bun.spawn call and invalid-PID
output assertion to pipe stderr, await proc.exited, and assert the child’s
stderr is empty. After validating stdout, assert the strongest completion
invariant by requiring proc.exitCode === 0, preserving the existing output
snapshot.
---
Outside diff comments:
In `@src/jsc/Debugger.rs`:
- Around line 605-642: Update Debugger::create failure handling in
start_at_runtime so a thread-spawn error rolls back all startup state, including
HAS_CREATED_DEBUGGER and has_started_debugger, before returning the error. Keep
vm.debugger cleared and preserve can_start_at_runtime() so later SIGUSR1,
process._debugProcess, or inspector.open() attempts can retry successfully.
In `@src/jsc/RuntimeInspector.rs`:
- Around line 229-235: Update the SIGUSR1 setup in platform::apply to check the
return values from sigemptyset and sigaction, preserving the previous signal
disposition when either syscall fails. Propagate failure from platform::apply
and report the corresponding OS error instead of recording StartInspector or
Ignore as successfully applied.
In `@test/js/bun/runtime-inspector/helpers.ts`:
- Around line 33-42: Update the waitForBanner read loop to track when timeout
wins Promise.race, cancel the pending reader.read() operation, and await its
completion before propagating the timeout error. Ensure reader.releaseLock()
runs only after the cancellation protocol completes, while preserving normal
stream completion behavior.
🪄 Autofix
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: 9ed2b61d-957c-4547-91ff-0619f1dd77d1
📒 Files selected for processing (14)
docs/runtime/debugger.mdxdocs/snippets/cli/run.mdxsrc/jsc/Debugger.rssrc/jsc/RuntimeInspector.rssrc/jsc/VM.rssrc/jsc/VirtualMachine.rssrc/jsc/bindings/BunDebugger.cppsrc/jsc/bindings/BunProcess.cppsrc/options_types/context.rssrc/runtime/cli/Arguments.rssrc/runtime/jsc_hooks.rstest/js/bun/runtime-inspector/helpers.tstest/js/bun/runtime-inspector/runtime-inspector-posix.test.tstest/js/bun/runtime-inspector/runtime-inspector.test.ts
💤 Files with no reviewable changes (1)
- src/options_types/context.rs
runSnippet drains stdout and stderr and returns them with the exit code, so debugProcess and the argument-validation tests assert on all three instead of stdout alone.
|
On the three "outside the diff" review items:
|
There was a problem hiding this comment.
I reviewed this revision and the bug hunting system found no new issues; all prior inline findings from earlier runs are addressed (30ef98e, 388ab9e, 9a5d8dd). Given the scope — a new cross-thread trap/signal path into JSC, Windows CreateRemoteThread plumbing, and a dependency on an unmerged WebKit change (WEBKIT_VERSION still points at the PR-287 preview build) — a human review is still warranted before this lands.
What was reviewed:
- Async-signal-safety of the SIGUSR1 handler (only
sem_post) andSemaphore::waitEINTR/KERN_ABORTED retry loops. onDebuggerTrap/runWhilePausedre-entrancy:inPauseLoopgating, snapshot-under-lock beforedoDisconnectmutates the map, and theanyPausedguard against re-enteringbreakProgram._debugProcessargument validation (matchesprocess.kill'spid | 0check) and Windows handle cleanup on each error path.- SIGUSR1 hand-back ordering in
onDidChangeListenersand thegc_owns_sigusr1FreeBSD carve-out.
Extended reasoning...
Overview
This PR wires runtime inspector activation via SIGUSR1 / process._debugProcess(pid), matching Node.js. It adds a new src/jsc/RuntimeInspector.rs module (POSIX semaphore + dedicated thread; Windows named file mapping + CreateRemoteThread), a per-VM NeedDebuggerBreak trap callback in BunDebugger.cpp that connects/drains/pauses from inside a busy JS loop, a real Process_functionDebugProcess in BunProcess.cpp, two new CLI flags (--inspect-port, --disable-sigusr1) plumbed through four command entry points, EINTR-safe Bun::Semaphore::wait, docs, and a full test suite. It also refactors the existing inspector.open() path onto a shared start_at_runtime helper.
Security risks
The Windows _debugProcess path opens the target with PROCESS_CREATE_THREAD | PROCESS_VM_* and calls CreateRemoteThread at a function pointer read from a named mapping. This is Node's own protocol and is gated by OS process ACLs, but it is still remote-thread injection into another process and merits a maintainer glance. The POSIX side installs a process-wide SIGUSR1 handler at VM init unless --disable-sigusr1 is passed; the handler is correctly async-signal-safe (single sem_post), and the FreeBSD/JSC-GC-owns-SIGUSR1 case is checked. _debugProcess argument validation rejects non-positive-int32 pids before any syscall.
Level of scrutiny
High. This is ~650 lines of new native code coordinating a signal handler, a dedicated thread, JSC VM traps, and the debugger connection state machine across three threads (signal → SignalInspector → JS), on all three platforms. BunDebugger.cpp gains a new re-entrancy dimension (onDebuggerTrap running inside arbitrary JS via VMTraps, guarded by inPauseLoop). The change also hooks every main-thread event-loop tick (check_and_activate_inspector). None of this is mechanical.
Other factors
WEBKIT_VERSIONis pointed atautobuild-preview-pr-287-d55f967e, an unmerged WebKit PR. The description says it will be repointed before merge; a human should confirm that has happened.- This is the third iteration of the feature (replaces #34106 and #26867), so the design has already had review cycles, but the implementation is fresh on current main.
- Test coverage is thorough (idle/busy activation, pause-in-while(true), reconnect, listener precedence/hand-back,
--inspect*interaction,--disable-sigusr1, error paths on both platforms) and every prior review comment on this PR is resolved. - The
Semaphore::waitchange also affects existing callers ofBun::Semaphore(now retries on EINTR/KERN_ABORTED instead of returning false), which is almost certainly a fix but is a behaviour change outside this feature.
|
On the |
|
Updated 6:39 PM PT - Aug 10th, 2026
❌ @robobun, your commit 192eb9e has 2 failures in
🧪 To try this PR locally: bunx bun-pr 37336That installs a local version of the PR into your bun-37336 --bun |
|
Went through oven-sh/WebKit#287 against this branch (192eb9e) and requested changes there: oven-sh/WebKit#287 (review). The JSC-side problems are on that PR; this is the list of things that land on this side, either because the fix belongs here or because a test here is the only thing that would pin the WebKit hunk. All from reading, none of it run.
Tests I'd add before this merges, since right now reverting DebuggerCallFrame.cpp:159, the |
| const pending = new Map<number, PromiseWithResolvers<any>>(); | ||
| ws.onmessage = event => { | ||
| const msg = JSON.parse(event.data as string); | ||
| if (msg.id === undefined) { | ||
| onEvent?.(msg); | ||
| return; | ||
| } | ||
| const request = pending.get(msg.id); | ||
| pending.delete(msg.id); | ||
| if (msg.error) request?.reject(new Error(`CDP error: ${JSON.stringify(msg.error)}`)); | ||
| else request?.resolve(msg); | ||
| }; | ||
| return function send(method: string, params: Record<string, unknown> = {}): Promise<any> { | ||
| const id = nextId++; | ||
| const request = Promise.withResolvers<any>(); | ||
| pending.set(id, request); | ||
| ws.send(JSON.stringify({ id, method, params })); | ||
| return withTimeout(`response to ${method}`, request.promise); | ||
| }; |
There was a problem hiding this comment.
🟡 cdpClient sets ws.onmessage but never ws.onclose/ws.onerror, so if the inspected target crashes mid-request the pending promise only fails via withTimeout after 20s with a generic "Timed out waiting for response to " instead of surfacing the close reason — REVIEW.md's "Wire EVERY failure event (error, close, abort, process exit) to reject the awaited promise". Add ws.onclose/ws.onerror handlers that reject and clear every entry in pending.
Extended reasoning...
What the bug is
cdpClient() at test/js/bun/runtime-inspector/helpers.ts:158-176 builds a request/response layer over the inspector WebSocket:
export function cdpClient(ws: WebSocket, onEvent?: (msg: any) => void) {
let nextId = 1;
const pending = new Map<number, PromiseWithResolvers<any>>();
ws.onmessage = event => { ... };
return function send(method, params = {}) {
const id = nextId++;
const request = Promise.withResolvers<any>();
pending.set(id, request);
ws.send(JSON.stringify({ id, method, params }));
return withTimeout(`response to ${method}`, request.promise);
};
}Only ws.onmessage is set. Nothing wires ws.onclose or ws.onerror to the pending map. connectInspector() does set ws.onerror, but that handler's reject is a no-op once ws.onopen has already resolved the connection promise, so it provides no coverage during CDP traffic — and ws.onclose is never set at all.
REVIEW.md's "Tests reviewers reject" section states:
Wire EVERY failure event (
error,close,abort, process exit) to reject the awaited promise
This is a direct match for that rule.
Step-by-step proof
- A test calls
send("Runtime.evaluate", { expression: "6 * 7" }). APromiseWithResolversis created and stored inpendingunder id 1;ws.send(...)writes the request; the caller awaitswithTimeout("response to Runtime.evaluate", request.promise, 20_000). - The inspected target aborts before responding — e.g. exactly the scenario this PR documents at
helpers.ts:77-82(inspecteeEnvcomment): underBUN_JSC_validateExceptionChecks, answeringRuntime.evaluateruns JSC's InjectedScript, whose unchecked exception scope aborts the process. Or the target isSIGKILLed, or crashes for any other reason. - The debugger-thread WebSocket server dies with the target; the client-side
wsreceives a close frame (or the TCP connection resets). ws.oncloseis unset → nothing runs.ws.onerror(fromconnectInspector) callsrejecton an already-settled promise → no-op.ws.onmessagenever fires again.request.promiseinpendingis never settled.pendingstill holds the entry.- 20 seconds later,
withTimeout's timer fires and rejects with"Timed out after 20000ms waiting for response to Runtime.evaluate". - The test fails with a misleading timeout message instead of "WebSocket closed: ", and 20s of wall-clock is burned per in-flight request.
Why existing code doesn't prevent it
withTimeout(20_000) is the only bound. It is a safety-net fallback, not the failure-event wiring the rule requires — the rule's point is that a close/error is an observable condition that should reject immediately, whereas the timeout only proves "nothing happened for 20s". connectInspector's onerror is spent once the connection resolves. Nothing else touches pending on close.
Impact
Bounded — this is failure-path only. On the happy path nothing changes; withTimeout guarantees the awaited promise settles within 20s so tests do not hang to the test timeout. But on a systematic failure (a broken build where every target aborts on the first CDP round trip), each affected describe.concurrent test burns 20s and reports a generic timeout instead of the actual close reason, which makes the CI failure harder to diagnose. The known trigger (InjectedScript exception-check abort) is already worked around via inspecteeEnv, so this only bites on unexpected target crashes. Hence nit, not blocking.
Fix
Add close/error handlers that flush pending:
const pending = new Map<number, PromiseWithResolvers<any>>();
const fail = (why: string) => {
for (const [, req] of pending) req.reject(new Error(`CDP connection ${why}`));
pending.clear();
};
ws.onclose = e => fail(`closed (${e.code} ${e.reason || ""})`);
ws.onerror = e => fail(`errored: ${(e as ErrorEvent).message ?? e}`);
ws.onmessage = event => { ... };This surfaces the close reason immediately and lets the test fail with a diagnostic message the moment the target dies, instead of 20s later with "Timed out".
|
Thanks, agreed on all eight. Plan, so you can redirect early if any of it is wrong: WebKit#287 (same PR, new revision, then the pin here moves to its preview):
This side:
|
Sending
SIGUSR1to a running bun process (or callingprocess._debugProcess(pid), which also works on Windows) starts the inspector, matching Node.js. This works even when the JS thread is stuck inwhile (true) {}and never returns to the event loop.Clean replacement for #34106 (and #26867 before it): one commit on current main, with every item deferred during the #34106 review folded in. Requires oven-sh/WebKit#287;
WEBKIT_VERSIONpoints at its preview build until it lands and gets repointed at the merged sha before this merges.How it works
After activation, the debugger thread fires the same trap whenever it queues a message for the target, so CDP keeps flowing into a busy loop and the debugger thread never blocks on the target. An idle target is handled by the event-loop wakeup instead (one relaxed load per tick on the hot path). No StopTheWorld, no polling traps, no bootstrap-pause state machine.
breakProgram()is needed because code that was running before activation was compiled withoutop_debugsites; the WebKit side adds the callback hook plusDebugger::isPauseAtNextOpportunitySet()so an in-flight step-over is not mistaken for a pause request.Behaviour
kill -USR1 <pid>/process._debugProcess(pid)start the inspector and print the usual banner (default port6499).--inspect-port=[host:]portpre-selects where it listens (same forms as--inspect,0for a free port);--disable-sigusr1leaves the signal untouched. Both also apply tobun build --compilebinaries.process.on("SIGUSR1")listener takes the signal over; removing the last listener restores whatever the process started with (the activation handler, orSIG_IGNunder--inspect*), tracked asruntime_inspector::Sigusr1.--inspect*processes ignoreSIGUSR1. Platforms where JSC's GC ownsSIGUSR1(FreeBSD) are left untouched._debugProcesserrors:ERR_MISSING_ARGS,ERR_INVALID_ARG_TYPE/ERR_INVALID_ARG_VALUEfor anything that is not a positive int32 (samepid != (pid | 0)check asprocess.kill), a system error with.code/.syscallon POSIX, and Node's Win32 message text on Windows (vendoredtest-debug-process.jspasses).inspector.open()(landed on main since July) and this path now shareDebugger::start_at_runtime, so both install Bun's inspector controller.Verification
Covers: idle and
while(true)activation,Debugger.pauseinterruptingwhile(true)(12/12 runs on the debug ASAN build, so it is no longer skipped on sanitizer lanes), no double activation (acknowledged via a CDP round trip rather than a sleep), reconnect, self-signal, user listener precedence, hand-back to the activation handler and back toSIG_IGNunder--inspect,--inspect*ignoring the signal,--disable-sigusr1, and the argument/error paths. All fail on a build without this change (_debugProcessis a stub there).test/js/node/inspector/inspector.test.tsstill passes on the sharedstart_at_runtimepath, andbun run rust:check-allis clean on all ten targets.no test proof · iteration 14 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/runtime-inspector/runtime-inspector-posix.test.ts test/js/bun/runtime-inspector/runtime-inspector.test.ts test/js/node/process/process.test.js