Skip to content

Error.captureStackTrace: lazily compute .stack header on non-Error targets - #35640

Open
robobun wants to merge 6 commits into
mainfrom
farm/2631d62a/capture-stack-trace-non-error-header
Open

Error.captureStackTrace: lazily compute .stack header on non-Error targets#35640
robobun wants to merge 6 commits into
mainfrom
farm/2631d62a/capture-stack-trace-non-error-header

Conversation

@robobun

@robobun robobun commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Fixes #16418. Fixes the .stack header for the jsonwebtoken JsonWebTokenError pattern reported in #13904.

When Error.captureStackTrace(target) is called with a target that is not a native ErrorInstance (a plain object, or a function-based Error subclass whose prototype is Object.create(Error.prototype)), Bun eagerly formatted the stack string at capture time and hardcoded "Error" as the header, ignoring the target's own .name/.message. V8 installs a lazy accessor and derives the header from .name/.message at first .stack access, so setting them after capture (as jsonwebtoken does) must be observable:

var JsonWebTokenError = function (message) {
  Error.call(this, message);
  Error.captureStackTrace(this, this.constructor);
  this.name = 'JsonWebTokenError';   // set AFTER capture
  this.message = message;
};
JsonWebTokenError.prototype = Object.create(Error.prototype);
JsonWebTokenError.prototype.constructor = JsonWebTokenError;

try { throw new JsonWebTokenError('Hello world'); } catch (e) { console.log(e.stack); }

Before:

Error
    at hello (/tmp/repro.js:15:15)
    ...

After (matches Node):

JsonWebTokenError: Hello world
    at hello (/tmp/repro.js:15:11)
    ...

The fix

The non-ErrorInstance branch of errorConstructorFuncCaptureStackTrace now mirrors what the ErrorInstance branch already does: build the source-mapped CallSite array at capture time, stash it on the target under a private name, and install a lazy custom accessor on .stack. The new getter reads .name/.message via the Error.prototype.toString algorithm (so {name: undefined}"Error", {name: ""} → message only, etc.) and consults Error.prepareStackTrace at first access instead of at capture time, both matching V8.

Also fixes the header in formatStackTraceToJSValue to read .name from the error object instead of hardcoding "Error: ", so the temporary string seen inside a prepareStackTrace callback has the correct header too.

Note: #13904 accumulated two distinct reports. This PR fixes the original JsonWebTokenError .stack header; #32136 addresses the separate zod/tail-call frame-clearing symptom from the later comment.

How did you verify your code works?

New tests in test/js/node/v8/capture-stack-trace.test.js:

  • captureStackTrace on a non-Error object reads name/message lazily for the stack header (the jsonwebtoken shape)
  • captureStackTrace on a non-Error object installs a lazy accessor (descriptor matches Node's {get, set, enumerable: false, configurable: true})
  • captureStackTrace header on a non-Error object matches V8's Error.prototype.toString algorithm (11 header edge cases verified byte-for-byte against Node)
  • captureStackTrace on a non-Error object invokes Error.prepareStackTrace at access time (the Error.prepareStackTrace is called on Error.captureStackTrace when it shouldn't be #16418 repro)

All four fail on the unfixed build; all 47 tests in the file pass with the fix. The existing "Error.captureStackTrace installs .stack as non-enumerable" test is updated to assert that prepareStackTrace runs at .stack access time for plain-object targets (verified against Node), since it previously asserted on the eager behavior this PR removes.


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

fails on main (without fix)
ASAN without fix: 6 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" "test/js/node/v8/capture-stack-trace.test.js"
bun test v1.4.0 (644df87c1)

test/js/node/v8/capture-stack-trace.test.js:
(pass) Regular .stack [25.76ms]
(pass) throw inside Error.prepareStackTrace doesnt crash [13.25ms]
(pass) capture stack trace [13.40ms]
(pass) capture stack trace with message [16.59ms]
(pass) capture stack trace with constructor [10.87ms]
(pass) capture stack trace limit [42.37ms]
(pass) prepare stack trace [21.69ms]
(pass) capture stack trace second argument [36.23ms]
(pass) capture stack trace edge cases [19.99ms]
338 |   };
339 |   try {
340 |     const o2 = {};
341 |     Error.captureStackTrace(o2);
342 |     // V8 invokes prepareStackTrace lazily on first .stack access, not at capture time.
343 |     expect(insidePrepare).toBeUndefined();
                                ^
error: expect(received).toBeUndefined()

Received: {
  keys: [],
  enumerable: false,
}

      at <anonymous> (/workspace/bun/test/js/node/v8/capture-stack-trace.test.js:343:27)
(fail) Error.captureStackTrace installs .stack as non-enumerable [
... (truncated)

release without fix: all passed
bun test v1.4.0-canary.1 (9b1d1eb7f)

test/js/node/v8/capture-stack-trace.test.js:
(pass) Regular .stack [3.12ms]
(pass) throw inside Error.prepareStackTrace doesnt crash [3.49ms]
(pass) capture stack trace [0.87ms]
(pass) capture stack trace with message [0.17ms]
(pass) capture stack trace with constructor [0.14ms]
(pass) capture stack trace limit [0.46ms]
(pass) prepare stack trace [0.17ms]
(pass) capture stack trace second argument [0.30ms]
(pass) capture stack trace edge cases [0.19ms]
(pass) Error.captureStackTrace installs .stack as non-enumerable [1.23ms]
(pass) prepare stack trace call sites [0.26ms]
(pass) sanity check [0.22ms]
(pass) CallFrame isEval works as expected [1.36ms]
(pass) CallFrame isTopLevel returns false for Function constructor [0.87ms]
(pass) CallFrame.p.getThisgetFunction: strict/sloppy mode interaction [0.23ms]
(pass) CallFrame.p.isConstructor [0.08ms]
(pass) CallFrame.p.isNative [0.06ms]
(pass) return non-strings from Error.prepareStackTrace [0.06ms]
(pass) CallFrame.p.toString [0.06ms]
(pass) err.stack should invoke prepareStackTrace [0.43ms]
(pass) Error.prepareStackTrace inside a node:vm works [11.22ms]
(pass) Error.captureStackTrace 
... (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/v8/capture-stack-trace.test.js"
bun test v1.4.0 (644df87c1)

test/js/node/v8/capture-stack-trace.test.js:
(pass) Regular .stack [23.75ms]
(pass) throw inside Error.prepareStackTrace doesnt crash [12.76ms]
(pass) capture stack trace [12.20ms]
(pass) capture stack trace with message [12.76ms]
(pass) capture stack trace with constructor [9.45ms]
(pass) capture stack trace limit [40.52ms]
(pass) prepare stack trace [19.39ms]
(pass) capture stack trace second argument [30.55ms]
(pass) capture stack trace edge cases [17.50ms]
(pass) Error.captureStackTrace installs .stack as non-enumerable [36.46ms]
(pass) prepare stack trace call sites [19.75ms]
(pass) sanity check [21.38ms]
(pass) CallFrame isEval works as expected [13.91ms]
(pass) CallFrame isTopLevel returns false for Function constructor [13.16ms]
(pass) CallFrame.p.getThisgetFunction: strict/sloppy mode interaction [15.55ms]
(pass) CallFrame.p.isConstructor [5.75ms]
(pass) CallFrame.p.isNative [6.05ms]
(pass) return non-strings from Error.prepareStackTrace [6.48ms]
(pass)
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 1792ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/138] gen ErrorCode+*.h
[2/138] gen bindgenv2
[3/138] gen BunProcess.lut.h
Generating /workspace/bun/build/release/codegen/BunProcess.lut.h from /workspace/bun/src/jsc/bindings/BunProcess.cpp
[4/138] gen cpp.rs (cppbind)
[5/138] gen ZigGeneratedClasses.{cpp,h,rs}
Found 2 classes from /workspace/bun/src/jsc/resolve_message.classes.ts
  - ResolveMessage (13 fields)
  - BuildMessage (10 fields)
Found 1 classes from /workspace/bun/src/runtime/api/Archive.classes.ts
  - Archive (4 fields, 1 class fields)
Found 2 classes from /workspace/bun/src/runtime/api/BunObject.classes.ts
  - ResourceUsage (8 fields)
  - Subprocess (20 fields)
Found 1 classes from /workspace/bun/src/runtime/api/cron.classes.ts
  - CronJob (5 fields)
Found 3 classes from /workspace/bun/src/runtime/api/filesystem_router.classes.ts
  - FileSystemRouter (5 fields)
  - FrameworkFileSystemRouter (2 fields)
  - MatchedRoute (8 fields)
Found 1 classes from /workspace/bun/src/runtime/api/Glob.classes.ts
  - Glob (5 fields)
Found 1 classes from /w
... (truncated)
diff hotspot
src/js/builtins/BunBuiltinNames.h           |   1 +
 src/jsc/bindings/CallSite.cpp               |   4 +
 src/jsc/bindings/FormatStackTraceForJS.cpp  | 134 +++++++++++++++++++---
 src/jsc/bindings/FormatStackTraceForJS.h    |   2 +
 src/jsc/bindings/ZigGlobalObject.cpp        |   5 +
 src/jsc/bindings/ZigGlobalObject.h          |   1 +
 test/js/node/v8/capture-stack-trace.test.js | 169 +++++++++++++++++++++++++++-
 7 files changed, 299 insertions(+), 17 deletions(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                         reads  edits  tests
src/js/builtins/BunBuiltinNames.h                1      1      0
src/jsc/bindings/CallSite.cpp                    1      1      0
src/jsc/bindings/FormatStackTraceForJS.cpp       6     15      0
src/jsc/bindings/FormatStackTraceForJS.h         2      3      0
src/jsc/bindings/ZigGlobalObject.cpp             2      2      0
src/jsc/bindings/ZigGlobalObject.h               1      1      0
test/js/node/v8/capture-stack-trace.test.js      5      8      0

…rgets

When the target of Error.captureStackTrace is not a native JSC ErrorInstance
(a plain object, or a function-based Error subclass whose prototype is
Object.create(Error.prototype), as used by jsonwebtoken), Bun eagerly
formatted the stack string at capture time with a hardcoded "Error" header,
ignoring the target's own .name/.message. V8 installs a lazy accessor and
reads name/message at first .stack access, so setting them after capture is
observable.

This makes the non-ErrorInstance path match the existing ErrorInstance path:
build the sourcemapped CallSite array at capture time, stash it under a
private name on the target, and install a lazy custom getter that reads
name/message (via Error.prototype.toString's algorithm) and consults
Error.prepareStackTrace at first access.

Also fixes formatStackTraceToJSValue's header to read .name instead of
hardcoding "Error: ", so the header inside prepareStackTrace callbacks is
correct for any target.

Fixes the .stack property for the JsonWebTokenError pattern in #13904.
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 seconds

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7e91f726-6147-4b9f-8f6d-43fc1bbeea30

📥 Commits

Reviewing files that changed from the base of the PR and between df6c7ee and 644df87.

📒 Files selected for processing (7)
  • src/js/builtins/BunBuiltinNames.h
  • src/jsc/bindings/CallSite.cpp
  • src/jsc/bindings/FormatStackTraceForJS.cpp
  • src/jsc/bindings/FormatStackTraceForJS.h
  • src/jsc/bindings/ZigGlobalObject.cpp
  • src/jsc/bindings/ZigGlobalObject.h
  • test/js/node/v8/capture-stack-trace.test.js

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

@github-actions

Copy link
Copy Markdown
Contributor

Found 2 issues this PR may fix:

  1. Axios fails on startup due to a type mismatch on captureStackTrace #15750 - Axios/follow-redirects calls Error.captureStackTrace(this, this.constructor) on a plain object (non-ErrorInstance), which Bun rejected with TypeError: First argument must be an Error object. This PR fixes captureStackTrace to work with non-ErrorInstance targets.
  2. Error.prepareStackTrace is called on Error.captureStackTrace when it shouldn't be #16418 - Error.prepareStackTrace is invoked eagerly at captureStackTrace() time instead of lazily at .stack access time. This PR defers prepareStackTrace consultation to the lazy .stack getter, matching V8/Node behavior.

If this is helpful, copy the block below into the PR description to auto-close these issues on merge.

Fixes #15750
Fixes #16418

🤖 Generated with Claude Code

@robobun

robobun commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 11:23 AM PT - Jul 25th, 2026

@autofix-ci[bot], your commit 644df87 has 1 failures in Build #80956 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 35640

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

bun-35640 --bun

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. Derive the stack trace header from the error's name and message #33437 - Also derives the .stack header from .name/.message instead of hardcoding "Error" for non-ErrorInstance targets passed to captureStackTrace (eager approach vs this PR's lazy getter)
  2. error: read name/message via full [[Get]] for the .stack header; drop inspect.js workaround #34868 - Also switches the .stack header to read .name/.message via full [[Get]] on non-ErrorInstance targets, fixing the same hardcoded "Error" bug

🤖 Generated with Claude Code

@robobun

robobun commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author

Re the duplicate flags: #33437 and #34868 both fix the hardcoded "Error" header by reading .name/.message off the target eagerly at capture/format time. That fixes {name, message} literals but not the jsonwebtoken case from #13904 where name/message are assigned after captureStackTrace, and it doesn't address #16418 (prepareStackTrace running at capture time).

This PR installs a lazy accessor instead, so the header and prepareStackTrace are both evaluated at first .stack access, which is what V8 does and what those two repros require. The formatStackTraceToJSValue header change here does overlap with both; whichever lands first, the other will need a small rebase there.

Comment thread src/jsc/bindings/FormatStackTraceForJS.cpp
Comment thread src/jsc/bindings/FormatStackTraceForJS.cpp
Comment thread src/jsc/bindings/FormatStackTraceForJS.cpp
Comment thread src/jsc/bindings/FormatStackTraceForJS.cpp
…e re-entry, proto-chain lookup, dedicated setter

CallSite::formatAsString now emits "async " for await-chain frames,
matching V8's CallSite.prototype.toString() and preserving the prefix
that Bun::formatStackTrace already rendered on the pre-lazy path.

The non-ErrorInstance lazy getter now clears the private CallSite slot
before reading name/message so a getter that reads this.stack terminates
at the !callSites guard instead of recursing to stack overflow, walks
the prototype chain so Object.create(target).stack resolves, and is
paired with a dedicated setter that releases the CallSite array when
.stack is overwritten before first read.
Comment thread src/jsc/bindings/FormatStackTraceForJS.cpp Outdated
Comment thread src/jsc/bindings/FormatStackTraceForJS.cpp Outdated
Comment thread src/jsc/bindings/FormatStackTraceForJS.cpp 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.

All four issues from my earlier review are addressed in 51978f9 (detach-before-reentry, async prefix in CallSite::formatAsString, proto-chain walk in the getter, dedicated setter that clears the private slot), each with a regression test, and the bug hunter found nothing new on this revision. Deferring to a human because this is ~200 lines of new JSC binding code on the error/stack-trace path — new CustomAccessor pair, private-name GC root on arbitrary targets, user-code re-entry ordering — and the comment-cop flags on the three multi-line comments are still open.

What was reviewed:

  • Verified the isInsideErrorPrepareStackTraceCallback flag is reset before RETURN_IF_EXCEPTION, so a throwing name/message getter or prepareStackTrace doesn't leave it stuck.
  • Checked the new setter's receiver-based putDirectchild.stack = x on an inheriting object shadows on the child and leaves the parent's lazy state intact, which matches V8's [[Set]] semantics.
  • Confirmed m_nonErrorLazyStackCustomGetterSetter is registered in FOR_EACH_GLOBALOBJECT_GC_MEMBER so it's visited.
Extended reasoning...

Overview

The PR changes Error.captureStackTrace(target) for non-ErrorInstance targets from eagerly formatting .stack at capture time to installing a lazy CustomAccessor, so the header (.name/.message) and Error.prepareStackTrace are consulted at first access — matching V8 and fixing #16418 and the jsonwebtoken half of #13904. It touches FormatStackTraceForJS.cpp (new getter/setter, factored-out buildSourceMappedCallSitesArray, header now reads .name via Error.prototype.toString semantics), CallSite.cpp (emit async prefix), ZigGlobalObject.{h,cpp} (new lazy CustomGetterSetter property), BunBuiltinNames.h (capturedStackTrace private name), and adds seven tests to capture-stack-trace.test.js.

Security risks

None identified. The change only affects string formatting of stack traces. It does invoke user getters (.name/.message) and prepareStackTrace from native code, but that surface already existed on the ErrorInstance path and the eager path when prepareStackTrace was set; the re-entrancy guard (detach private slot before formatting) now prevents unbounded recursion.

Level of scrutiny

High. This is C++ in the JSC bindings on a path exercised by essentially every error in the runtime. It introduces a new GC-visible slot (a JSArray of CallSite objects rooted on arbitrary user objects via a private name), a new CustomAccessor pair, prototype-chain walking with getPrototypeDirect, and ordering constraints around user-code invocation. The REVIEW.md memory-safety section ("Anything that can run user JS can synchronously free your state") applies directly. The four issues my first pass found — re-entrancy stack overflow, dropped async prefix, retained CallSite array on setter, proto-chain miss — were all real regressions in the initial revision, which is a signal that this area is subtle enough to want maintainer eyes.

Other factors

  • All four prior findings were fixed in 51978f9 and each has a dedicated regression test in the same file.
  • The refactor of computeErrorInfoWithPrepareStackTrace into buildSourceMappedCallSitesArray + wrapper is a mechanical extraction; I diffed the moved body and it's identical apart from the return point.
  • The new LazyPropertyOfGlobalObject<CustomGetterSetter> is added to FOR_EACH_GLOBALOBJECT_GC_MEMBER, so it's visited — no missing write-barrier wiring.
  • The three open comment-cop flags (lines ~790/801/819) are on comments that document ordering invariants and the reason for the proto-chain walk; they read as legitimate invariant comments per REVIEW.md rather than narration, but that's a maintainer call.
  • Two potentially-duplicating open PRs (#33437, #34868) take the eager-header approach; this PR supersedes them for the lazy-access cases and will conflict on formatStackTraceToJSValue's header block.

The sentinel that breaks re-entry is restored to the CallSite array if
user code (name/message getter, prepareStackTrace) throws during
formatting, so a later .stack read retries instead of returning
undefined. Matches Node, which re-invokes the getter on the next access.

Also trim the multi-line comments flagged by comment-cop.
Comment thread src/jsc/bindings/FormatStackTraceForJS.cpp
Comment thread src/jsc/bindings/FormatStackTraceForJS.cpp
…Site array while the accessor is still installed

The isNull sentinel now halts the prototype walk so a re-entrant read
on a child cannot materialize an ancestor's capture. On the exception
path, the CallSite array is restored only when .stack is still the
custom accessor; if prepareStackTrace already replaced it with the
default-formatted string, the array is released instead of being
orphaned.
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/js/internal/inspector/cdp.ts Outdated
Comment thread src/jsc/bindings/BunDebugger.h Outdated
@robobun
robobun force-pushed the farm/2631d62a/capture-stack-trace-non-error-header branch from 43b0e1a to 644df87 Compare July 25, 2026 14:52

@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.

Additional findings (outside current diff — PR may have been updated during review):

  • 🟡 src/js/internal/inspector/cdp.ts:1-11 — This PR also adds src/js/internal/inspector/cdp.ts (702 lines) and src/jsc/bindings/BunDebugger.h, which are unrelated in-progress node:inspector work — nothing in the tree imports inspector/cdp or #includes BunDebugger.h, and the four jsFunction_*NodeInspector* host functions it declares have no definitions anywhere. Both files landed in commit 102103d whose message only mentions the proto-chain/sentinel fix, so this looks like an accidental inclusion; please drop them from this PR and land them with the inspector work.

    Extended reasoning...

    What the issue is

    The PR's stated scope is the lazy .stack header for Error.captureStackTrace on non-Error targets — changes to FormatStackTraceForJS.{cpp,h}, CallSite.cpp, ZigGlobalObject.{cpp,h}, BunBuiltinNames.h, and capture-stack-trace.test.js. However, the diff also adds two entirely unrelated new files:

    • src/js/internal/inspector/cdp.ts (702 lines) — an InspectorCDPAdapter class that translates between the V8 Chrome DevTools Protocol and JSC's inspector protocol
    • src/jsc/bindings/BunDebugger.h (15 lines) — declares four host functions: jsFunction_openNodeInspector, jsFunction_waitForNodeInspectorConnection, jsFunction_postNodeInspectorControl, jsFunction_closeNodeInspector

    Neither file has anything to do with Error.captureStackTrace or the .stack header.

    Step-by-step proof that these are dead / accidental

    1. Nothing references them. rg 'InspectorCDPAdapter|inspector/cdp|jsFunction_openNodeInspector|jsFunction_waitForNodeInspectorConnection|jsFunction_postNodeInspectorControl|jsFunction_closeNodeInspector|BunDebugger\.h' over the whole tree matches only the two files themselves. No .cpp file #includes BunDebugger.h; no module imports internal/inspector/cdp.
    2. The declared host functions have no definitions. BunDebugger.h uses JSC_DECLARE_HOST_FUNCTION for four symbols, but there is no corresponding JSC_DEFINE_HOST_FUNCTION anywhere in the tree. These are header-only declarations with no backing implementation.
    3. They arrived in an unrelated commit. git log -- src/js/internal/inspector/cdp.ts src/jsc/bindings/BunDebugger.h shows both files were added in 102103d6, whose message is "stop proto-chain walk at the re-entry sentinel; only restore the CallSite array while the accessor is still installed" — a captureStackTrace follow-up with no mention of inspector/CDP work.
    4. The PR description doesn't mention them. The description lists only the FormatStackTraceForJS/CallSite/ZigGlobalObject/test changes.
    5. The comment-cop bot flagged cdp.ts 18 times and BunDebugger.h once in the PR timeline — a strong signal these files weren't reviewed as part of this change.

    Why nothing prevents it

    The build passes because dead code compiles: JSC_DECLARE_HOST_FUNCTION is a forward declaration and doesn't require a definition unless the symbol is referenced (it isn't), and cdp.ts bundles cleanly as an internal module even though nothing imports it. So CI is green despite 717 lines of unreachable in-progress feature work riding along.

    Impact

    No runtime failure — the code is unreachable, which is why this is filed as a nit rather than blocking. But merging as-is has real costs:

    • Misleading git history. git blame on these files will forever point at a captureStackTrace PR, and when the actual node:inspector PR lands later it will show as a diff against half-finished code that was already merged.
    • Dead declarations. BunDebugger.h declares four host functions that don't exist — anyone grepping for them later will find a header with no implementation.
    • Review scope violation. Per REVIEW.md, focused bugfix PRs shouldn't carry unrelated feature work ("don't ride file-wide standardization on a focused bugfix"), and dead code shouldn't land (the "delete dead code in the same PR that makes it dead" rule; here it's the inverse — adding dead code).

    How to fix

    git rm src/js/internal/inspector/cdp.ts src/jsc/bindings/BunDebugger.h and amend/push. These files should land in their own node:inspector PR alongside the .cpp that defines the four host functions and whatever imports InspectorCDPAdapter.

@robobun

robobun commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author

Those two files were an accidental inclusion from a stale worktree and are already removed in 644df87; the diff is back to the intended 7 files.

Comment thread src/jsc/bindings/FormatStackTraceForJS.cpp
@robobun

robobun commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author

CI status on 644df87 (build 80956): capture-stack-trace.test.js passes on every lane. The red lanes are unrelated flakes (fastutf8stream-reopen on ubuntu, webview-chrome click timing on debian, 20144 signal on darwin, all marked flaky by the annotator) plus a darwin-x64 build-bun step that failed outside the runner. Diff is green on the tests it touches; ready for a maintainer.

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.

Error.prepareStackTrace is called on Error.captureStackTrace when it shouldn't be

1 participant