Skip to content

Error.captureStackTrace throws 'First argument must be an Error object' after a delegating Error.prepareStackTrace wrapper is installed #41151

Open

Description

@brynne8

Bug description

On Bun (JSC), once a delegating wrapper is installed on Error.prepareStackTrace — i.e. any wrapper that calls the original native prepareStackTrace — every subsequent call to Error.captureStackTrace(...) throws:

TypeError: First argument must be an Error object

even though the first argument really is an Error instance (instanceof Error === true, correct prototype chain).

The same code runs fine on Node.js (V8). This breaks real-world packages that combine stack-rewriting (Babel, source-map-support) with V8-style stack capture (follow-redirects, etc.): a running example is @vue/cli-service serve (webpack 4 toolchain) failing to boot under Bun.

Minimal reproduction

// min.js — Bun v1.4.0: exits 1 with the TypeError below
// Node.js: prints "OK"

const old = Error.prepareStackTrace
// Delegating wrapper — the standard pattern, used e.g. by
// @babel/core/lib/errors/rewrite-stack-trace.js and source-map-support
Error.prepareStackTrace = (err, trace) => old(err, trace)

// V8-style stack capture (as used by follow-redirects' createErrorType)
function CustomError() {
  Error.captureStackTrace(this, this.constructor)
}
CustomError.prototype = new Error()

new CustomError()
console.log("OK")
$ bun min.js
TypeError: First argument must be an Error object
      at new CustomError (/private/tmp/min.js:11:9)
      at /private/tmp/min.js:15:1

Bun v1.4.0 (macOS x64)

Trigger conditions (minimized by subtraction)

Variant Result
No wrapper, plain Error.captureStackTrace(this, this.constructor) ✅ OK
Wrapper installed that does not call the original (e.g. () => String(err), or one that merely closes over it) ✅ OK
Wrapper installed that calls the original prepareStackTrace ((err, trace) => old(err, trace), with 1 or 2 args, named or anonymous, installed before or after the first captureStackTrace call) ❌ throws

So the trigger is specifically: a Error.prepareStackTrace wrapper that delegates to the native implementation, followed by any Error.captureStackTrace call. The wrapper does not need to be invoked between the two; mere installation is enough.

Real-world impact

Under Bun, @vue/cli-service serve (Vue CLI 4 / webpack 4) crashes at startup:

  1. @vue/cli-plugin-babel apply → babel.loadPartialConfigSync() permanently installs Babel's prepareStackTrace rewriter (which delegates to the previous one)
  2. later, webpack-dev-server loads follow-redirects, whose createErrorType constructor calls Error.captureStackTrace(this, this.constructor) → process exits with TypeError: First argument must be an Error object

Workaround applied in the meantime: wrap Error.captureStackTrace in try/catch before starting the CLI (degrades to losing a few internal stack frames).

Environment

  • Bun v1.4.0, macOS x64 (Darwin 23.6.0)
  • Node.js comparison: same script prints OK

Activity

  1. robobun commented on Sep 2, 2026

    @robobun
    Collaborator

    Reproduced: the repro throws TypeError: First argument must be an Error object on 1.4.1 canary, Node prints OK. The native default Error.prepareStackTrace rejected any target that is not a native Error instance.
    Fix in progress: #41153

  2. robobun commented on Sep 5, 2026

    @robobun
    Collaborator

    The fix is in #41395 (the default Error.prepareStackTrace accepts any object, as Error.captureStackTrace does). I verified the repro from this issue against that branch: new CustomError() no longer throws and .stack is "Error", the same as Node. #41153 was a duplicate of it and is closed.

  3. added
    bugSomething isn't working
    node.jsCompatibility with Node.js APIs
    on Sep 20, 2026
  4. CaliLuke commented on Nov 3, 2026

    @CaliLuke

    Still reproducible with Bun 1.4.4 (5d9cc0f4f) on macOS arm64. Instrumenting source with [email protected] installs Babel's stack formatter and triggers the same failure for prototype-based error constructors:

    import { createInstrumenter } from "istanbul-lib-instrument";
    
    createInstrumenter({ esModules: true }).instrumentSync(
      "export function example() { return 1; }",
      "/tmp/example.js",
    );
    
    function CustomError() {
      Error.captureStackTrace(this, this.constructor);
    }
    CustomError.prototype = new Error();
    new CustomError();

    Run with bun repro.mjs. Expected: construction succeeds. Actual: TypeError: First argument must be an Error object. This also prevents [email protected] from loading after instrumentation. Moving instrumentation into a separate worker avoids the process-global formatter interaction without changing the error constructor or suppressing stack capture.

  5. ton618to commented on Nov 8, 2026

    @ton618to

    Additional downstream impact: this prevents SAP AI Core provider imports in OpenCode's source-run server, not just stack display. Report and independent upstream reproduction: anomalyco/opencode#54791 (comment).

    The path is OpenTUI/Solid → Babel's process-global stackTraceRewriter, followed by @sap-cloud-sdk/util → axios → follow-redirects. The import fails in follow-redirects' createErrorType / CustomError at Error.captureStackTrace(this, this.constructor), before any SAP authentication or model request. OpenCode's released 2.0.26 binary passed initialization in the upstream comparison; source execution with the Solid preload failed.

    I also reduced it to Babel's public API, with no OpenCode, SAP SDK, credentials, or network calls needed after dependency installation:

    // repro.cjs; dependency: @babel/[email protected]
    const { transformSync } = require('@babel/core')
    const original = Error.prepareStackTrace
    transformSync('export const value = 1', { configFile: false, babelrc: false })
    console.log('formatter changed:', Error.prepareStackTrace !== original)
    
    function CustomError() {
      Error.captureStackTrace(this, this.constructor)
    }
    CustomError.prototype = new Error()
    new CustomError()
    console.log('OK')

    Observed locally on macOS arm64:

    • Bun 1.4.4: formatter changed: true, then TypeError: First argument must be an Error object.
    • Node 24.13.0: formatter changed: true, then construction succeeds.
    • Bun 1.4.4, restoring the captured formatter immediately after transformSync as a diagnostic control: construction succeeds.

    Babel already tracks its global formatter leak in babel/babel#16099. Linking these rather than opening a duplicate. The native formatter compatibility fix in #41395 remains the underlying runtime fix; globally swallowing stack-capture exceptions is not needed to reproduce or diagnose this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingnode.jsCompatibility with Node.js APIs

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions