Skip to content

Infinite recursion between TriggerUncaughtException and InspectorConsoleCall when console.error throws #64326

Description

@themuuln

Version

v26.3.0 (also hit this on v22 macOS 15)

Platform

Darwin TS-Temuulen-2.local 25.5.0 Darwin Kernel Version 25.5.0: Fri Jun 5 02:15:17 PDT 2026; root:xnu-11471.122.99.501.2~1/RELEASE_ARM64_T6000 arm64 arm

Subsystem

V8 / Inspector / Process

What steps will reproduce the bug?

Hard to nail down to a single script — it's non-deterministic. But here's what triggers it:

A native addon (in my case Turbopack via napi-rs ThreadSafeFunction) calls a JS callback that has a pending exception. Instead of handling the error normally, the process locks up.

I caught it with sample while it was spinning at 94% CPU. Here's the stack:

uv_run → uv__io_poll → uv__async_io
  → ThreadSafeFunction::AsyncCb
    → CallbackScope::~CallbackScope
      → InternalCallbackScope::Close
        → v8::Function::Call
          → Execution::Call → Invoke
            → Isolate::ReportPendingMessages
              → MessageHandler::ReportMessage
                → TriggerUncaughtException
                  → v8::Function::Call
                    → Execution::Call → Invoke
                      → Builtins_JSEntry
                        → InspectorConsoleCall
                          → v8::Function::Call  (← re-enters TriggerUncaughtException)

The pattern is clear: TriggerUncaughtException reports via the inspector console, InspectorConsoleCall itself throws, and that throw goes right back into TriggerUncaughtException. Forever.

You'd probably need a napi-rs addon with a ThreadSafeFunction that calls into JS and throws, with the inspector/profiler attached during the call. Something like:

const addon = require('bindings')('addon.node')
// addon.startWorker(callbackThatThrows) while inspector is connected

How often does it reproduce? Is there a required condition?

Non-deterministic. Roughly 1 in 3 dev server restarts for me. Required conditions:

  • A native addon callback that throws
  • Inspector protocol active (dev tools or Node inspector)
  • Inspector connection has to be in a state where InspectorConsoleCall fails

What is the expected behavior? Why is that the expected behavior?

TriggerUncaughtException should not be re-entrant through the inspector. If the inspector console itself throws during error reporting, Node should detect the re-entrance and either:

  • Fall back to writing to stderr directly (bypasses inspector entirely)
  • Or crash with process.abort() so at least it's obvious

A console.error inside error handling should never be able to loop back into TriggerUncaughtException. That path should be guarded.

What do you see instead?

The process pegs a CPU core at 94-100%. RSS starts around 150MB and grows to 2.2GB+ — keeps allocating because V8's exception machinery keeps running. The machine starts swapping. kill (SIGTERM) does nothing, have to kill -9 it. Meanwhile the entire system slows to a crawl from memory pressure.

Additional information

  • The workaround is replacing console.error with process.stderr.write in error handlers — it bypasses the inspector protocol breakage entirely
  • Same loop pattern reported in cdp-tools-mcp may be it's good idea go tell something about this fork? #74 (v0.4.3 patch)
  • This isn't specific to Next.js or Turbopack — any native addon with async callbacks can trigger it under the right conditions

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions