fix(js): hide node_modules/async-separator frames and stop attach from applying dependency source maps (#655) - #661
Merged
Merged
Conversation
…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
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #655
Problem
get_stack_traceon a js attach session paused inside the mcp-debugger HTTP server returned 66 of 91 frames withincludeInternals: false: ~40node_modules/.pnpm/…router/body-parser/hono frames, ~10 phantom relative../src/*.tspaths (unopenable, for the MCP SDK and for mcp-debugger's own code), and<unknown_source>await/Promise.thenseparators. One frame mattered.Root causes (verified against a
DAP_TRACE=1capture and the vendored js-debug 1.112)JsDebugAdapterPolicy.isInternalFramematched only<node_internals>.transformAttachConfigset no source-map keys. js-debug's pwa-node attach default forresolveSourceMapLocationscollapses tonull—applyNodeDefaultscopies an undefinedoutFilesover the['**','!**/node_modules/**']default before the spread, and the later filter turns it intonull(= resolve maps everywhere) — so every dependency's.js.mapwas applied and their unshipped.tssources became the frame paths.sourceswithout a base path, and its attach path leavescwdundefined — so even mcp-debugger's owndist/**/*.js.map(sources: ["../../src/x.ts"], which exist next todist/) came back as../src/x.tslabels withsourceReference != 0.Changes
packages/shared/src/interfaces/adapter-policy-js.ts): internal =<node_internals>/node:, anynode_modulespath segment (pnpm and Windows layouts,file://URLs;/app/src/node_modules_helper.jsstays visible), or a sourceless line-0 async separator.frame.namenever 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).packages/adapter-javascript/src/javascript-debug-adapter.ts):resolveSourceMapLocations: ['**','!**/node_modules/**']andcwd(server cwd, orMCP_WORKSPACE_ROOTin container mode) when absent; an explicit caller value — includingresolveSourceMapLocations: null— wins (incheck). The policy attach branch carries the sameresolveSourceMapLocationsguard 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).cwdonly gates resolution: with a deliberately wrongcwd: /tmpthe debuggee's frames still resolved to the correct absolutesrc/**/*.ts, because sources resolve against the map's own location.skipFilesis deliberately untouched: blackboxingnode_modulesin 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 withsourceReference != 0and a real-looking path carriesunresolvedSource: true, and the responsenotesays itsfileis 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, sopath.resolve(dirname(generated), relative)is not implementable fromget_stack_traceinputs; with the two defaults above it only fires for a debuggee whose maps point at sources that genuinely are not on disk.includeInternalsschema description (24 snapshots regenerated, one hunk),changelog.d/655.fixed.md.Verification
it.eachtable (15 cases),filterStackFramesno-fallback, policy attach guard (default / caller list / callernull), transform defaults (+ container-mode cwd), resolver JS case with the exact frame shapes from the trace (unresolvedSourceset/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).dist/server/tool-result.js:13) on the mcp-debugger HTTP server undernode --inspect, through a server built from this branch:../srcpathsEvery shown frame is an openable absolute path (
dist/**/*.jsorsrc/**/*.ts);includeInternals: trueshows the dependency frames at their realnode_modules/**/*.jspaths.🤖 Generated with Claude Code
https://claude.ai/code/session_01DP1W22KJbkHZi4ueAtMFPF