Skip to content

node:inspector: derive scriptParsed isModule and scriptLanguage from JSC's scriptType - #39051

Merged
alii merged 3 commits into
mainfrom
farm/12f15c7f/inspector-cdp-script-type
Aug 17, 2026
Merged

node:inspector: derive scriptParsed isModule and scriptLanguage from JSC's scriptType#39051
alii merged 3 commits into
mainfrom
farm/12f15c7f/inspector-cdp-script-type

Conversation

@robobun

@robobun robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • Every Debugger.scriptParsed event that the inspector.open() DevTools server sends has isModule: false, ES modules included, and a WebAssembly.Module is announced with scriptLanguage: "JavaScript".
  • src/js/internal/inspector/cdp.ts:583 builds the event from params.module, a parameter JSC's Debugger.scriptParsed does not have, so it is always undefined.
  • module comes from an older WebKit protocol; packages/bun-inspector-protocol's JSC protocol snapshot still lists it, but the WebKit Bun builds replaced it. Regenerating that snapshot is a separate change; nothing else in the repo reads module.
  • JSC reports the kind of script as scriptType, one of "program", "module" or "webassembly" (vendor/WebKit/Source/JavaScriptCore/inspector/protocol/Debugger.json; scriptTypeForScript in InspectorDebuggerAgent.cpp maps Bun's Module and BunTranspiledModule source providers to "module" and CommonJS files, which Bun evaluates as programs, to "program").

Fix

  • Derive both CDP fields from scriptType: isModule is scriptType === "module", scriptLanguage is "WebAssembly" for "webassembly" and "JavaScript" otherwise.
  • This matches what V8 sends for the same scripts: isModule: true only for ES modules (CommonJS files are programs in both engines), and isModule: false with scriptLanguage: "WebAssembly" for wasm modules. The adapter forwards JSC's classification instead of reading a field its protocol never carries.
  • Test: test/js/node/inspector/inspector.test.ts, "Debugger.scriptParsed reports isModule and scriptLanguage from JSC's scriptType". The fixture is its own CDP client: it enables the Debugger domain, imports an .mjs and a .cjs file, compiles an empty WebAssembly.Module, and the test checks the three events. On the current release it fails with isModule: false for esm.mjs and scriptLanguage: "JavaScript" for the wasm module; it passes with this change.
  • bun bd test test/js/node/inspector/inspector.test.ts: 24 pass.
  • The test is appended after the existing breakpoint test in the file that test: run the node:inspector child-process tests concurrently and assert their full output #39031 is reorganizing; whichever of the two lands second needs a small rebase.

Background

  • inspector.open() serves the V8 Chrome DevTools Protocol (CDP). Bun's debugger backend speaks JSC's inspector protocol, and src/js/internal/inspector/cdp.ts translates between the two for each connection.
  • Debugger.scriptParsed exists in both protocols and is emitted once per script the engine compiles. CDP clients read isModule to know a script is an ES module (DevTools labels it as one and evaluates console input in module terms) and scriptLanguage to tell JavaScript from WebAssembly.
  • While a debugger is attached, JSC also reports every compiled WebAssembly.Module as a script (Debugger::sourceParsed(JSGlobalObject*, JSWebAssemblyModule*)), naming it <n>.wasm when the module has no source URL. That is the wasm event the test checks.
  • The in-process inspector.Session goes through the same adapter but discards events, so only the inspector.open() WebSocket path observes this.

[review] gate passed · iteration 0 · 2 files touched

fails on main (without fix)
ASAN without fix: 1 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/inspector/inspector.test.ts
bun test v1.4.0 (8326d1bd3)

test/js/node/inspector/inspector.test.ts:
(pass) inspector.url() [15.42ms]
(pass) inspector.console [2.30ms]
(pass) inspector.close() is a no-op when the inspector is not open [3.87ms]
(pass) inspector.waitForDebugger() throws ERR_INSPECTOR_NOT_ACTIVE when the inspector is not active [5.65ms]
(pass) inspector.open() serves the DevTools protocol and /json discovery endpoints [4173.97ms]
(pass) inspector.close() followed by inspector.open() starts a new server [1877.02ms]
(pass) inspector.open() can be retried after a failed start [1815.56ms]
(pass) inspector.open() with wait=true does not hang the process after a bind failure [1692.55ms]
(pass) inspector.waitForDebugger() blocks until a client resumes the process [2060.32ms]
(pass) inspector.waitForDebugger() blocks again on the second call after a frontend disconnects [2083.45ms]
hello 42
after disable
(pass) Runtime.consoleAPICalled is emitted while the Runtime domain is enabled [62.44ms]
-0 NaN Infinity -Infinity 1
... (truncated)

release without fix: 1 FAILED
bun test v1.4.0-canary.1 (8326d1bd3)

test/js/node/inspector/inspector.test.ts:
(pass) inspector.url() [0.04ms]
(pass) inspector.console [0.01ms]
(pass) inspector.close() is a no-op when the inspector is not open [0.04ms]
(pass) inspector.waitForDebugger() throws ERR_INSPECTOR_NOT_ACTIVE when the inspector is not active [0.08ms]
(pass) inspector.open() serves the DevTools protocol and /json discovery endpoints [76.19ms]
(pass) inspector.close() followed by inspector.open() starts a new server [33.44ms]
(pass) inspector.open() can be retried after a failed start [31.94ms]
(pass) inspector.open() with wait=true does not hang the process after a bind failure [30.99ms]
(pass) inspector.waitForDebugger() blocks until a client resumes the process [36.69ms]
(pass) inspector.waitForDebugger() blocks again on the second call after a frontend disconnects [36.49ms]
hello 42
after disable
(pass) Runtime.consoleAPICalled is emitted while the Runtime domain is enabled [1.27ms]
-0 NaN Infinity -Infinity 1n
(pass) Runtime.consoleAPICalled encodes -0/NaN/Infinity/bigint as unserializableValue like Node [0.16ms]
(pass) Session errors carry Node's ERR_INSPECTOR_* codes and post() vali
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/inspector/inspector.test.ts
bun test v1.4.0 (8326d1bd3)

test/js/node/inspector/inspector.test.ts:
(pass) inspector.url() [14.94ms]
(pass) inspector.console [2.46ms]
(pass) inspector.close() is a no-op when the inspector is not open [3.37ms]
(pass) inspector.waitForDebugger() throws ERR_INSPECTOR_NOT_ACTIVE when the inspector is not active [5.64ms]
(pass) inspector.open() serves the DevTools protocol and /json discovery endpoints [4114.05ms]
(pass) inspector.close() followed by inspector.open() starts a new server [1739.05ms]
(pass) inspector.open() can be retried after a failed start [1781.90ms]
(pass) inspector.open() with wait=true does not hang the process after a bind failure [1674.87ms]
(pass) inspector.waitForDebugger() blocks until a client resumes the process [2074.98ms]
(pass) inspector.waitForDebugger() blocks again on the second call after a frontend disconnects [2125.42ms]
hello 42
after disable
(pass) Runtime.consoleAPICalled is emitted while the Runtime domain is enabled [64.36ms]
-0 NaN Infinity -Infinity 1
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     94d4d58b7b
  features     baseline

22 deps, 120 codegen, 1175 objects in 686ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1236] gen ErrorCode+*.h
[2/1236] install /workspace/bun
bun install v1.4.0-canary.1 (8326d1bd3)

Checked 107 installs across 153 packages (no changes) [11.00ms]
[3/1236] gen bindgenv2
[4/1236] install /workspace/bun/packages/bun-error
bun install v1.4.0-canary.1 (8326d1bd3)

Checked 1 install across 2 packages (no changes) [1.00ms]
[5/1236] fetch libjpeg-turbo
[libjpeg-turbo] up to date
[6/1236] install /workspace/bun/src/node-fallbacks
bun install v1.4.0-canary.1 (8326d1bd3)

Checked 129 installs across 147 packages (no changes) [6.00ms]
[7/1236] fetch zlib
[zlib] up to date
[8/1236] fetch tinycc
[tinycc] up to date
[9/1235] gen .bind.ts → GeneratedBindings.cpp
[10/1235] gen ProcessBindingConstants.lut.h
Generating /workspace/bun/build/release/codegen/ProcessBindingConstants.lut.h from /workspace/bun/src/jsc/bindings/ProcessBindingConstants.cpp

... (truncated)
diff hotspot
src/js/internal/inspector/cdp.ts         |  5 ++-
 test/js/node/inspector/inspector.test.ts | 73 ++++++++++++++++++++++++++++++++
 2 files changed, 76 insertions(+), 2 deletions(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                      reads  edits  tests
src/js/internal/inspector/cdp.ts              1      2      0
test/js/node/inspector/inspector.test.ts      0      0      0

root cause · written by the author bot

The CDP adapter was not translating JSC's script metadata into the fields V8-style clients expect, so Debugger.scriptParsed events misreported ES modules and WebAssembly scripts as plain JavaScript programs. JSC describes a script with a single scriptType value of "program", "module", or "webassembly", while the CDP protocol splits that same information into isModule and scriptLanguage. The fix reads scriptType and maps it accordingly, setting isModule only for module scripts and reporting scriptLanguage as "WebAssembly" for wasm scripts, with integration tests covering ESM, CommonJS, and W…

…JSC's scriptType

The CDP adapter read a `module` parameter that JSC's Debugger.scriptParsed
does not have, so every script was reported with isModule: false. JSC
carries this as scriptType (program, module or webassembly); use it for
isModule and for scriptLanguage.
@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 302e6cfb-93c5-46c5-bcf2-31bde9f059d3

📥 Commits

Reviewing files that changed from the base of the PR and between 60a33de and 94d4d58.

📒 Files selected for processing (1)
  • src/js/internal/inspector/cdp.ts
💤 Files with no reviewable changes (1)
  • src/js/internal/inspector/cdp.ts

Included review availability: Your plan includes up to 5 reviews per rolling hour; 1 remains after this review.


Walkthrough

The inspector adapter derives Debugger.scriptParsed module and language metadata from JSC scriptType. Integration coverage verifies ESM, CommonJS, and WebAssembly scripts.

Changes

Script metadata translation

Layer / File(s) Summary
Translate script types to CDP metadata
src/js/internal/inspector/cdp.ts
The adapter reads scriptType, marks only module scripts as modules, and reports WebAssembly scripts with scriptLanguage: "WebAssembly". Other scripts remain JavaScript.
Validate parsed-script metadata
test/js/node/inspector/inspector.test.ts
The integration test loads ESM, CommonJS, and WebAssembly scripts and verifies their isModule and scriptLanguage values.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change to derive scriptParsed metadata from JSC's scriptType.
Description check ✅ Passed The description explains the problem, fix, scope, test coverage, and verification results, although it does not use the template headings exactly.

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Status: reproduced on the released bun with the new test in test/js/node/inspector/inspector.test.ts (isModule: false for an imported .mjs file, scriptLanguage: "JavaScript" for a WebAssembly.Module); passes with this branch. Waiting on CI.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — two-line adapter fix that reads JSC's actual scriptType field instead of a nonexistent module field.

What was reviewed:

  • Confirmed the mapping (scriptType === "module"isModule, "webassembly"scriptLanguage: "WebAssembly") matches V8's CDP shape; undefined scriptType falls back to the old behavior (false/"JavaScript").
  • Noted packages/bun-inspector-protocol/src/protocol/jsc/protocol.json still lists the old module: boolean — that file is a stale copy for the VS Code extension's typegen, not what the runtime backend sends; robobun's repro confirms the live backend emits scriptType.
  • Test covers all three script kinds, follows the neighboring fixture pattern, and uses an ordered command reply as the flush barrier so the event ordering assertion is deterministic.
Extended reasoning...

Overview

Two files touched. In src/js/internal/inspector/cdp.ts, the Debugger.scriptParsed translation now reads params.scriptType (which JSC's backend actually sends) instead of params.module (which it doesn't), and derives both isModule and scriptLanguage from it. The test file gains one new test that spawns a fixture acting as its own CDP client, loads an .mjs, a .cjs, and an empty WebAssembly.Module, and asserts the three scriptParsed events carry the right isModule/scriptLanguage.

Security risks

None. This is a read-only field mapping in the JSC→CDP protocol translator for the DevTools inspector. No user input is parsed, no auth/crypto/filesystem paths are touched.

Level of scrutiny

Low. The runtime change is two field expressions. If scriptType were somehow absent, both comparisons yield false and the output is identical to the pre-PR behavior (isModule: false, scriptLanguage: "JavaScript"), so there's no regression risk. I cross-checked the in-repo packages/bun-inspector-protocol/src/protocol/jsc/protocol.json, which still lists the legacy module: boolean field — but that's a stale snapshot used for the VS Code extension's type generation, not the live protocol; robobun's status comment confirms empirically that the released binary produces isModule: false for an .mjs (proving params.module is undefined at runtime) and this branch produces isModule: true (proving params.scriptType === "module" is present).

Other factors

The test mirrors the file's established fixture-as-its-own-CDP-client pattern (same tempDir/Bun.spawn/concurrent-drain shape as the neighboring tests), uses port: 0, and synchronizes on the ordered backend reply queue rather than sleeping. It asserts an ordered array, which is safe because each import is awaited sequentially and the trailing setBreakpointsActive reply proves all prior events have been delivered through the same ordered channel. The URL filter excludes the fixture itself and any replayed internal scripts from Debugger.enable. No CODEOWNERS cover these paths, no outstanding review comments, and the bug-hunting system found nothing.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 1:50 AM PT - Aug 17th, 2026

@robobun, your commit 94d4d58b7b03ac250d10dcd31e629b43fb0f6596 passed in Build #99922! 🎉


🧪   To try this PR locally:

bunx bun-pr 39051

That installs a local version of the PR into your bun-39051 executable, so you can run:

bun-39051 --bun

alii pushed a commit that referenced this pull request Aug 17, 2026
… pinned WebKit (#39110)

### Problem

- `packages/bun-inspector-protocol/src/protocol/jsc/protocol.json` and
the `index.d.ts` generated from it were last regenerated in November
2024 (#15261) and no longer describe the protocol the WebKit pinned in
`scripts/build/deps/webkit.ts` speaks.
- The visible symptom: `JSC.Debugger.ScriptParsedEvent` has `module?:
boolean`, but bun sends `executionContextId` and `scriptType`
(`"program" | "module" | "webassembly"`, the `Debugger.ScriptType` type
the snapshot lacks) and never sends `module`. Code written against the
types reads `params.module` and always gets `undefined` (the
`node:inspector` side of that is fixed separately in #39051, which does
not touch this package).
- Diffing the snapshot against the pinned WebKit's protocol also shows:
`LifecycleReporter.getModuleGraph` missing, the `BunFrontendDevServer`
and `HTTPServer` domains (both served by bun, see
`src/jsc/bindings/BunDebugger.cpp`) missing entirely,
`Console.ChannelSource` out of date, plus description and `targetTypes`
metadata changes. `TestReporter` was already current (#20645 updated it
by hand).
- Nothing regenerates the snapshot on a WebKit bump and no test compares
it with what bun sends, which is how it drifted.

### Fix

- Regenerated `jsc/protocol.json` and `jsc/index.d.ts` with
`scripts/generate-protocol.ts` from the `CombinedDomains.json` shipped
in the prebuilt `bun-webkit` tarball for `WEBKIT_VERSION` (`f0f60fd2`).
The only removed lines in `index.d.ts` are `module`, the `appcache`
channel and four description strings; everything else is additions. The
snapshot is the protocol the linked WebKit was built from, which is the
definition of what bun can send, so it is correct by construction; the
new test checks that against the running binary.
- `generate-protocol.ts`: locates the pinned WebKit's
`CombinedDomains.json` in the build cache when no path is given (so `bun
packages/bun-inspector-protocol/scripts/generate-protocol.ts` after a
`bun bd` is the whole regeneration), refreshes the V8 snapshot only with
`--v8` (needs network, and nothing in the repo imports it, so it is left
untouched here), removes the dead `getJSC()`, and skips the `File` and
`Process` domains: the WebKit fork declares them as
JavaScript-debuggable, but bun registers no agent for them and answers
`'Process' domain was not found`, so types for them would be as
misleading as the stale `module` was. The test pins both sides of that
list.
- `schema.d.ts`: `Domain.types` and `Event.parameters` are optional in
the actual JSON (`Audit` has no types, `Debugger.resumed` has no
parameters); `debuggableTypes` is what the generator filters on.
- `bun-debug-adapter-protocol/adapter.ts`: `DebugAdapterEventMap` was
`InspectorEventMap & ...`, so with the new domains it would advertise
`HTTPServer.*` / `BunFrontendDevServer.*` events the adapter never
re-emits (since #31559 it only forwards an allowlist of domains). It is
now derived from that allowlist; `isInspectorEvent` became the matching
type guard, which also removes the `as keyof JSC.EventMap` cast. No
runtime change. Type-checking `bun-debug-adapter-protocol`,
`bun-inspector-protocol` and the `bun-vscode` files that import `JSC`
types gives the same set of pre-existing errors before and after this
change.
- `.gitattributes`: the `linguist-generated` patterns still pointed at
the pre-#4345 `protocol/` layout, so they matched nothing; they now
cover `src/protocol/*/`, which is what collapses the two generated files
in this PR's diff.
- Test: `test/cli/inspect/bun-inspector-protocol.test.ts` starts `bun
--inspect-wait` on an ESM + CJS fixture, enables every domain in the
snapshot, pauses on a `debugger` statement, evaluates, and validates
every event and response it receives (recursively, through `$ref`s and
enums) against `protocol.json`; it also asserts the exact domain list
and that `File.enable` / `Process.enable` are still rejected, so
removing or adding a domain in the generator shows up here. Against the
old snapshot it fails with:
  ```
"Debugger.scriptParsed: property executionContextId is not in the
snapshot",
  "Debugger.scriptParsed: property scriptType is not in the snapshot",
  "LifecycleReporter.getModuleGraph: command is not in the snapshot",
  ```
and passes with the regenerated one (`bun bd test
test/cli/inspect/bun-inspector-protocol.test.ts`). The rest of
`test/cli/inspect/` passes as before (the `localhost` cases of
`inspect.test.ts` fail in this container with the released bun as well;
unrelated).
- Also verified: the generator is idempotent, its output equals the
per-domain JSON files in
`vendor/WebKit/Source/JavaScriptCore/inspector/protocol/` at the pinned
commit, and `--v8` and an explicit path argument still work.

### Background

- Inspector protocol: the JSON-RPC-style protocol spoken over `bun
--inspect`, organized into domains (`Debugger`, `Runtime`, ...) with
commands, events and types. WebKit defines each domain in
`inspector/protocol/<Domain>.json`; its build concatenates them into
`CombinedDomains.json` and generates the C++ dispatchers from that, so a
bun binary can only send what those files declare. Bun's fork adds
domains such as `LifecycleReporter`, `TestReporter`, `HTTPServer`,
`BunFrontendDevServer`, and bun registers an agent for each in
`BunDebugger.cpp`; a domain with no agent is rejected with `domain was
not found`.
- `bun-inspector-protocol` keeps a copy of the JavaScript-debuggable
subset of those definitions (`protocol.json`) and generates TypeScript
types from it (`JSC.<Domain>.<Name>`, plus `EventMap` / `RequestMap` /
`ResponseMap`). `bun-debug-adapter-protocol` (the VS Code debugger) and
`bun-vscode` are its consumers.
- `debuggableTypes` is the per-domain list in WebKit's JSON of which
kinds of targets a domain applies to; the generator keeps the domains
tagged `"javascript"`.
Comment thread src/js/internal/inspector/cdp.ts Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — two-line adapter fix reading the correct JSC protocol field, with a test covering all three scriptType values.

What was reviewed:

  • Verified against packages/bun-inspector-protocol/src/protocol/jsc/protocol.json: ScriptType is "program" | "module" | "webassembly" and scriptParsed carries scriptType (the old module field is gone after #39110), so the mapping is exhaustive.
  • CDP output values match V8's Debugger.ScriptLanguage enum ("JavaScript"/"WebAssembly") and isModule semantics (ES modules only; CJS is a program in both engines).
  • Test follows the file's existing fixture pattern, drains pipes concurrently, awaits an ordered command reply as the barrier instead of sleeping, and is hermetic.
Extended reasoning...

Overview

Two files touched. src/js/internal/inspector/cdp.ts changes two field values in the Debugger.scriptParsed translation: isModule now reads scriptType === "module" (previously !!params.module, a field JSC's protocol no longer emits, so it was always false), and scriptLanguage now reports "WebAssembly" when scriptType === "webassembly" (previously hardcoded "JavaScript"). test/js/node/inspector/inspector.test.ts adds one test that opens the inspector, connects as a CDP client, loads an .mjs, a .cjs, and an empty WebAssembly.Module, and asserts the three emitted events.

Security risks

None. This is metadata in debugger protocol events sent to an already-connected DevTools frontend; it does not affect execution, parsing, permissions, or any security boundary.

Level of scrutiny

Low. The production change is three lines (one destructure + two field expressions) in a protocol-translation adapter, and the mapping is mechanically derivable from the two protocol definitions. I confirmed the JSC side against the regenerated protocol snapshot at packages/bun-inspector-protocol/src/protocol/jsc/protocol.json:607-609ScriptType is exactly ["program", "module", "webassembly"], so === "module" and === "webassembly" cover the enum with "program" (and any undefined) falling through to isModule: false / "JavaScript", which is the correct default.

Other factors

The test is well-constructed per the repo's review criteria: it uses tempDir/bunEnv/bunExe, port: 0, drains stdout/stderr/exited concurrently, and awaits a command reply on the same ordered backend queue as the barrier for event arrival rather than sleeping. It exercises all three scriptType variants. The PR description states it fails on the released bun and passes with the change (USE_SYSTEM_BUN=1 verified by robobun). The comment-cop inline comment was addressed in 94d4d58 and the thread is resolved. No outstanding reviewer feedback.

@alii
alii enabled auto-merge (squash) August 17, 2026 16:55
@alii
alii merged commit 5261ca6 into main Aug 17, 2026
6 checks passed
@alii
alii deleted the farm/12f15c7f/inspector-cdp-script-type branch August 17, 2026 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants