Skip to content

fix(js): hide node_modules/async-separator frames and stop attach from applying dependency source maps (#655) - #661

Merged
debugmcpdev merged 2 commits into
mainfrom
fix/655-js-stack-frames
Sep 2, 2026
Merged

fix(js): hide node_modules/async-separator frames and stop attach from applying dependency source maps (#655)#661
debugmcpdev merged 2 commits into
mainfrom
fix/655-js-stack-frames

Conversation

@debugmcpdev

Copy link
Copy Markdown
Collaborator

Fixes #655

Problem

get_stack_trace on a js attach session paused inside the mcp-debugger HTTP server returned 66 of 91 frames with includeInternals: false: ~40 node_modules/.pnpm/… router/body-parser/hono frames, ~10 phantom relative ../src/*.ts paths (unopenable, for the MCP SDK and for mcp-debugger's own code), and <unknown_source> await/Promise.then separators. One frame mattered.

Root causes (verified against a DAP_TRACE=1 capture and the vendored js-debug 1.112)

  1. JsDebugAdapterPolicy.isInternalFrame matched only <node_internals>.
  2. transformAttachConfig set no source-map keys. js-debug's pwa-node attach default for resolveSourceMapLocations collapses to nullapplyNodeDefaults copies an undefined outFiles over the ['**','!**/node_modules/**'] default before the spread, and the later filter turns it into null (= resolve maps everywhere) — so every dependency's .js.map was applied and their unshipped .ts sources became the frame paths.
  3. js-debug resolves no relative source-map sources without a base path, and its attach path leaves cwd undefined — so even mcp-debugger's own dist/**/*.js.map (sources: ["../../src/x.ts"], which exist next to dist/) came back as ../src/x.ts labels with sourceReference != 0.

Changes

  • Policy (packages/shared/src/interfaces/adapter-policy-js.ts): internal = <node_internals>/node:, any node_modules path segment (pnpm and Windows layouts, file:// URLs; /app/src/node_modules_helper.js stays visible), or a sourceless line-0 async separator. frame.name never participates. The local first-frame fallback is dropped so the central [QUESTION] Go stack filtering can return an empty stack — should it keep the first frame like JS? #346 guarantee (allFramesInternal + note) engages for JS — it used to produce "N-1 hidden" with the wrong note. Unresolvable source-mapped frames are not internal (they are the debuggee's code).
  • Attach defaults (packages/adapter-javascript/src/javascript-debug-adapter.ts): resolveSourceMapLocations: ['**','!**/node_modules/**'] and cwd (server cwd, or MCP_WORKSPACE_ROOT in container mode) when absent; an explicit caller value — including resolveSourceMapLocations: null — wins (in check). The policy attach branch carries the same resolveSourceMapLocations guard for embedders that bypass the transform (the js attach to a Node process that forks children strands those children (second startDebugging target ignored, child waits forever) #501 pattern). cwd only gates resolution: with a deliberately wrong cwd: /tmp the debuggee's frames still resolved to the correct absolute src/**/*.ts, because sources resolve against the map's own location. skipFiles is deliberately untouched: blackboxing node_modules in V8 would re-open the js attach: pause_execution stays pending forever on an idle Node server — the stop never lands even once JS runs #513 pause step-chase on an idle server; hiding the frames in the policy gives the display benefit without touching pause/step semantics.
  • unresolvedSource (packages/shared/src/models/index.ts, frame-anchor-resolver.ts, inspection-tools.ts): a frame with sourceReference != 0 and a real-looking path carries unresolvedSource: true, and the response note says its file is a label, not an openable path. Placeholder paths (<node_internals>, <eval>) are not flagged. This is the honest remainder for item 3 of the issue — the DAP frame carries neither the generated path nor the map location, so path.resolve(dirname(generated), relative) is not implementable from get_stack_trace inputs; with the two defaults above it only fires for a debuggee whose maps point at sources that genuinely are not on disk.
  • Docs (stack-trace-filtering, tool-reference, usage, agent guide, adapter-policy-pattern, javascript README), the includeInternals schema description (24 snapshots regenerated, one hunk), changelog.d/655.fixed.md.

Verification

  • Unit: policy it.each table (15 cases), filterStackFrames no-fallback, policy attach guard (default / caller list / caller null), transform defaults (+ container-mode cwd), resolver JS case with the exact frame shapes from the trace (unresolvedSource set/not set, separators hidden, all-internal → allFramesInternal), handler note. npm run typecheck, typecheck:tests (ratchet unchanged), lint, changelog:check, tests/e2e/mcp-server-smoke-javascript-attach.test.ts (8/8).
  • Live, same breakpoint (dist/server/tool-result.js:13) on the mcp-debugger HTTP server under node --inspect, through a server built from this branch:
frames shown hidden phantom ../src paths
before 66 25 10
after 6 85 0

Every shown frame is an openable absolute path (dist/**/*.js or src/**/*.ts); includeInternals: true shows the dependency frames at their real node_modules/**/*.js paths.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DP1W22KJbkHZi4ueAtMFPF

CI Bot and others added 2 commits September 2, 2026 13:27
…m applying dependency source maps (#655)

get_stack_trace on a js attach session paused inside an express/MCP
server returned 66 of 91 frames with includeInternals:false: forty
node_modules/.pnpm router/body-parser frames, ten phantom relative
'../src/*.ts' paths, and <unknown_source> await/Promise.then
separators. One frame mattered.

Two causes, verified against the DAP trace and the vendored js-debug:

- JsDebugAdapterPolicy.isInternalFrame matched only <node_internals>.
  It now also hides any node_modules path *segment* (pnpm and Windows
  layouts, file:// URLs) and sourceless line-0 async separators, and
  drops its local first-frame fallback so the central #346 guarantee
  (allFramesInternal + note) engages for JS. frame.name never
  participates; unresolvable source-mapped frames are not internal.
- transformAttachConfig set no source-map keys. js-debug's pwa-node
  attach default for resolveSourceMapLocations collapses to null
  (applyNodeDefaults copies an undefined outFiles over it before the
  spread, then filters to null without a workspace folder), so every
  dependency's .js.map was applied and their unshipped .ts sources
  surfaced as phantom paths. Attach now defaults launch's
  ['**','!**/node_modules/**'] (an explicit caller value, including
  null, wins) — dependency frames report their real .js path. It also
  defaults cwd (server cwd, or the workspace root in container mode):
  js-debug resolves no relative map source without a base path, even
  the debuggee's own '../../src/x.ts' next to dist/; the value only
  gates the resolution (verified with a deliberately wrong cwd), so the
  debuggee's frames now resolve to the absolute src/**/*.ts. The policy
  attach branch carries the same resolveSourceMapLocations guard for
  embedders that bypass the transform. skipFiles is deliberately not
  defaulted: blackboxing node_modules would re-open the #513 pause
  step-chase on an idle server.
- Frames js-debug still cannot place on disk (sourceReference != 0 with
  a real-looking path) carry unresolvedSource: true and the response
  note says their file is a label, not an openable path.

Docs: stack-trace-filtering, tool-reference (includeInternals,
unresolvedSource, the attach defaults and why skipFiles stays), usage,
agent guide, adapter-policy-pattern, javascript README. Snapshots
regenerated for the includeInternals description.

Verified live against the mcp-debugger HTTP server under node --inspect
through a server built from this branch: the same breakpoint yields 6
frames (85 hidden), every one an openable absolute path; with
includeInternals:true the dependency frames show real
node_modules/**/*.js paths and no phantom ../src labels.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DP1W22KJbkHZi4ueAtMFPF
@debugmcpdev
debugmcpdev merged commit 41b4741 into main Sep 2, 2026
8 checks passed
@debugmcpdev
debugmcpdev deleted the fix/655-js-stack-frames branch September 2, 2026 13:37
@codecov

codecov Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

js get_stack_trace: includeInternals:false still returns ~50 node_modules frames, and source-mapped frames show unopenable relative '../src/…' paths

1 participant