Skip to content

js-debug: an adopted child's entry pause ("Paused on debugger statement") surfaces under load and is reported as a breakpoint #1097

Closed

Description

@debugmcpdev

Seen on 2026-11-06 in the pre-push hook's full integration run of #1096 (session tree M2 PR 2), once: javascript-subprocess-cascade.test.ts's cluster case starts a cluster primary with four workers under the default autoAttachChildProcesses, with one breakpoint at cluster_parent.js:29 (the worker branch). Three workers stopped on the breakpoint; the fourth's first stop was reported at line 9 — the module's first statement, const WORKERS = 4; — with lastStop.reason: 'breakpoint'. Every earlier run (local, Ubuntu CI, Windows CI) stopped on the breakpoint only.

What js-debug sent (the session's proxy log, worker cluster_parent.js [48076], child connection attached with continueOnAttach: true):

{"event":"stopped","body":{"reason":"pause","description":"Paused on debugger statement","threadId":3,"allThreadsStopped":false}}

No hitBreakpointIds. "Paused on debugger statement" is js-debug's generic description for a CDP pause with no breakpoint hit and no expected reason (_createPausedDetails's fallback), not only a literal debugger; statement. Under load, an adopted child's entry pause surfaced through that path instead of being resumed silently.

What the server did: JsDebugAdapterPolicy.normalizeStopReason turns reason: 'pause' + /debugger statement/ into 'breakpoint' (issue #672's rule, written for a real debugger; statement), so the answer said the worker stopped at a breakpoint on a line that has none:

[SessionManager cc56a3a0-…] Stop reason normalized: 'pause' -> 'breakpoint'

Why it matters: an agent reading reason: 'breakpoint' at a line without a breakpoint inspects the wrong place and distrusts the tool. Two possible answers, either or both:

  1. Keep the debugger; rule only when the stop's line is not the program's first statement, or only when body.hitBreakpointIds is absent and the top frame's line holds a debugger token — cheap to check from the source the server already reads for get_source_context.
  2. For an adopted js-debug child whose first stop is a pause at its first statement (no hit ids), auto-continue it the way handleAutoContinue treats an entry stop with stopOnEntry: false — the child was adopted with continueOnAttach: true, so the pause is never what the user asked for.

Workaround in the suites (#1096): the child-stop helper resumes a child stop that is not at the expected line and waits again, with the measurement in its comment.

Environment: Windows 11, js-debug v1.140.0, Node 24.14.1, the hook's full integration run (33 files) as the load.

Activity

  1. debugmcpdev commented on Nov 7, 2026

    @debugmcpdev
    CollaboratorAuthor

    Fix up as #1103: a first-stop pause / "Paused on debugger statement" on a launch that did not ask to stop on entry (the root launch and every adopted child) is now held — the session stays running, nothing is answered — while the server reads the source line under its top frame. A line without a debugger statement is js-debug's leaked module-entry break and is resumed unseen; a line with one is the breakpoint it always was (#672 holds); no line to go on (no path, a failed stack request or read) keeps the previous answer. The policy owns the shape (JsDebugAdapterPolicy.stopNeedsSourceLine, context.topFrameLine), the core does the bounded stackTrace + file read. The three JavaScript subprocess suites drop the resume loop that worked around this, so the next leak under the hook's load fails by name instead of being resumed.

    Of the two answers proposed above, this is (1) with the source check as the discriminator, applied before the stop is named rather than after; (2) alone would also have resumed a real debugger; at a child's first statement. The mechanism in the body is confirmed as far as the bundle reads: the entry break's CDP registration is not awaited, and continueOnAttach resumes only "Break on start" / "debugCommand" — the leaked pause is CDP reason other.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions