Skip to content

fix(javascript): catch the first call of a first-declared function breakpoint and explain a late entry stop (#858) - #863

Merged
debugmcpdev merged 1 commit into
mainfrom
fix/858-js-entry-stop
Oct 6, 2026
Merged

debugmcpdev merged 1 commit into
mainfrom
fix/858-js-entry-stop

Conversation

@debugmcpdev

@debugmcpdev debugmcpdev commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #858. Builds on #862 (merged; shares the CDP bridge). Upstream: microsoft/vscode-js-debug#2430.

What was wrong

js-debug implements stopOnEntry for a Node launch with Debugger.setBreakpointByUrl at line 0, column 0 of the program. V8 resolves that position with FindClosestSharedFunctionInfoFromPosition — the nearest breakable position in source order across every function intersecting the span up to the first top-level statement — so for a file whose first declaration is a function the program calls later, the breakpoint lands in that function's body. Measured with a js-debug trace (adapterLaunchConfig.trace) on the issue's file B: breakpointResolved at line 1 col 16 inside helper, the entry stop 3 s later at the timer's call, started already printed. And since the breakpoint is never removed, every later call of the function stops as entry again (file B with two calls: both stop).

Two things followed on our side:

  1. The silent first-call miss. A pre-launch JavaScript function breakpoint forces js-debug's entry stop to bind (js-debug function breakpoints via the CDP proxy (revives #282 by another route) #295) and auto-continues it. For a function-first file that stop is the function's first call, so the breakpoint was armed from inside the call it should have caught, the stop was resumed as an unrequested entry, and the only call ran on — list_breakpoints saying verified: true.
  2. A stopOnEntry launch stopped late, in the wrong place, with nothing saying so.

Also found: the adapter's --inspect-brk=9229 append for stopOnEntry was inert, and its promotion of a user's bare --inspect to --inspect-brk=9229 forced an entry stop — js-debug strips --inspect-brk out of runtimeArgs into stopOnEntry itself (resolveParams in the vendored bundle). Node's own --inspect-brk (callAndPauseOnStart, pause on the first executed statement) is not reachable through js-debug's bootloader path; attachSimplePort would give it but drops the bootloader, child-process debugging and the exit-code shim — rejected.

What changes

  • CDP bridge (src/proxy/cdp-function-breakpoint-bridge.ts): an entry stop whose paused frame's functionLocation equals the [[FunctionLocation]] of a function armed at that stop is reported as that function breakpoint (reason: 'function breakpoint', its adapter id in hitBreakpointIds), so the SessionManager never auto-continues the first call. Matched by position, never by name. An entry stop whose top frame is a named function — with or without function breakpoints — carries the explanation in the stopped event's text: where it landed, why (line 1 col 1 + V8's source-order resolution), that the top-level code before the call has run, that every later call stops as entry again, and what to do instead (a line breakpoint on the first statement). The text travels with lastStop into start_debugging, wait_for_stop, list_debug_sessions and get_stack_trace. The entry-stop gate in ChildSessionManager now lets entry stops reach the bridge with nothing armed.
  • JavaScript adapter: the inert --inspect-brk=9229 append and the --inspect → --inspect-brk promotion are gone; inspector flags pass through as given.
  • Docs (docs/javascript/README.md, skills/debugging/references/javascript.md), changelog fragment, and the JavaScript: the entry stop lands inside the first-declared function when the program calls it — stopOnEntry stops late, and a function breakpoint on it skips its first call #858 thread carry the mechanism and the upstream link.

Recorded, not built: a real fix — plant a hidden breakpoint on the first top-level statement (found with the vendored acorn) and swallow js-debug's own late entry — noted in #858 as the follow-up.

Verification

  • npm run typecheck:all (ratchet 0/0), npm run lint, node scripts/check-docs.mjs, pnpm changelog:check.
  • Unit: bridge fake-CDP cases (relabel by [[FunctionLocation]]; no relabel for a same-named function at another location; late-entry note with nothing armed; top-level entry untouched), child-session-manager (entry stops routed with nothing armed, other reasons not), adapter transform (no inspector flags added or rewritten). Full npm run test:unit green.
  • e2e, local Windows, on the new examples/javascript/function_first.js (function declared first, called 1.5 s in): a function breakpoint on work stops at its first call with late total not yet printed (previously ran to completion); a stopOnEntry launch answers paused/entry with lastStop.text naming work(). Plus the full mcp-server-smoke-js-function-bp, mcp-server-smoke-javascript, javascript-launch-config and mcp-server-logpoints suites.

🤖 Generated with Claude Code

Base automatically changed from fix/850-853-js-logpoints to main October 6, 2026 03:08
@debugmcpdev
debugmcpdev force-pushed the fix/858-js-entry-stop branch from b7490aa to f9283d4 Compare October 6, 2026 03:09
…eakpoint and explain a late entry stop (#858)

js-debug's stopOnEntry breakpoint sits at line 0 col 0 and V8 resolves it
to the first breakable position in source order — the body of a function
declared above the first statement — so the "entry" stop is that
function's first call and recurs at every later call
(microsoft/vscode-js-debug#2430). The forced entry stop that binds a
pre-launch function breakpoint was that first call, auto-continued and
lost. The CDP bridge now reports an entry stop whose paused frame is the
function it just armed (matched by [[FunctionLocation]]) as the function
breakpoint, and annotates an entry stop that landed inside a function in
the stopped event's text; ChildSessionManager routes entry stops to the
bridge with nothing armed. The adapter's inert --inspect-brk=9229 append
and its --inspect promotion are removed (js-debug strips --inspect-brk
into stopOnEntry itself).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@debugmcpdev
debugmcpdev force-pushed the fix/858-js-entry-stop branch from f9283d4 to b459e5d Compare October 6, 2026 03:15
@codecov

codecov Bot commented Oct 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@debugmcpdev
debugmcpdev merged commit 55fbfea into main Oct 6, 2026
11 checks passed
@debugmcpdev
debugmcpdev deleted the fix/858-js-entry-stop branch October 6, 2026 03:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants