diff --git a/CLAUDE.md b/CLAUDE.md index 5c0c9be3..973dc732 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -248,7 +248,7 @@ The codebase follows a **layered architecture with dependency injection** and ** - Collaborators see the facade through `OperationsContext` (`operations-context.ts`), whose members are late-bound arrows and getters — never captured references — so reassigning a facade method or writing a tunable on a live instance is visible to them; each takes the narrowest `Pick<>` slice it uses. Collaborator-to-collaborator dependencies are constructor arguments (`DebugLauncher(ctx, proxyLauncher, breakpoints, inFlight)`, `AttachController(ctx, proxyLauncher, breakpoints, pauseCoordinator, inFlight)`) held as captured instance references: their methods resolve at call time, so `vi.spyOn(internals(ops).proxyLauncher, 'start')` intercepts, but reassigning a collaborator field after construction is not observed. The facade owns one shared `InFlightGuard` (`in-flight-guard.ts`, issue #711) that the launcher and the attach controller both take: `startDebugging`, `restartDebugging`, `attachToProcess` and `detachFromProcess` each claim the session before their first await and release it in `finally`, so one launch-shaped call per session at a time; `restartDebugging` replays through the launcher's private `launch()` under its own claim, never through the facade's `startDebugging`. Shared per-request DAP helpers live in `dap-request-helpers.ts` - Coordinates ProxyManager instances (one per session) - Handles breakpoint management and queuing - - The session tree (issue #1081, `docs/architecture/session-tree.md`): a child the adapter reports (debugpy; on by default, `dapLaunchArgs: { subProcess: false }` opts a launch out) is adopted by `SessionManagerCore.adoptReportedChild` as a session of its own — `parentSessionId`, the parent's language and breakpoints, a child proxy view over the parent's worker wired with the same handlers — under the per-tree cap of `child-session-cap.ts` (`DEBUG_MCP_MAX_CHILD_SESSIONS`, default 32, `0` off; beyond it the child is released); `closeSession` closes a subtree deepest first, a root that ends with live children defers its worker's reap until the last ends (`stopOrDeferWorker`), and the launch-shaped tools and the mirror refuse a child id by name (`handlers/shared.ts` `childSessionRefusal`). The tree-aware wait: `ExecutionController.waitForStop` runs on `waitForSubtreeSettle` (`execution/session-state-wait.ts`, the `waitForSettle` primitive shared with `waitForSessionState`) over the core's subtree listeners (`onSubtreeStateChange`, fired for a session and each ancestor on every transition and removal) and `selectSettledInTree` (`execution/tree-settle.ts`, the four-rule read: the session itself when paused or errored, else the first paused or errored descendant depth first in adoption order, else the session itself when ended, else keep waiting; `describeTreeAnswer` builds `children`/`childrenReleased`). The answer fields: `sessionId` (the selected session; set by the wait handler in `handlers/execution-tools.ts`), `children`/`childrenReleased`/`childrenClosed` (hoisted beside `pending` by `handlers/shared.ts` `hoistTreeFields` on the wait, launch, restart and detach answers) and `processEnded`/`released` (spread by the `close_debug_session` handler from the `closeSessionReporting` outcome — the close outcome it answers from, `childrenClosed` included); `releaseDescendants` is the detach cascade (children detached deepest first with `terminateDebuggee: false`, their rows kept as `stopped` with `programEnd.data.released`, then the parent) + - The session tree (issue #1081, `docs/architecture/session-tree.md`): a child the adapter reports (debugpy and js-debug; on by default for launches and verified-local JavaScript attaches, `dapLaunchArgs: { subProcess: false }` or `{ autoAttachChildProcesses: false }` opts a launch out, `adapterConfig: { autoAttachChildProcesses: false }` a JavaScript attach) is adopted by `SessionManagerCore.adoptReportedChild` as a session of its own — `parentSessionId`, the parent's language and breakpoints, a child proxy view over the parent's worker wired with the same handlers — under the per-tree cap of `child-session-cap.ts` (`DEBUG_MCP_MAX_CHILD_SESSIONS`, default 32, `0` off; beyond it the child is released); `closeSession` closes a subtree deepest first, a root that ends with live children defers its worker's reap until the last ends (`stopOrDeferWorker`), and the launch-shaped tools and the mirror refuse a child id by name (`handlers/shared.ts` `childSessionRefusal`). The tree-aware wait: `ExecutionController.waitForStop` runs on `waitForSubtreeSettle` (`execution/session-state-wait.ts`, the `waitForSettle` primitive shared with `waitForSessionState`) over the core's subtree listeners (`onSubtreeStateChange`, fired for a session and each ancestor on every transition and removal) and `selectSettledInTree` (`execution/tree-settle.ts`, the four-rule read: the session itself when paused or errored, else the first paused or errored descendant depth first in adoption order, else the session itself when ended, else keep waiting; `describeTreeAnswer` builds `children`/`childrenReleased`). The answer fields: `sessionId` (the selected session; set by the wait handler in `handlers/execution-tools.ts`), `children`/`childrenReleased`/`childrenClosed` (hoisted beside `pending` by `handlers/shared.ts` `hoistTreeFields` on the wait, launch, restart and detach answers) and `processEnded`/`released` (spread by the `close_debug_session` handler from the `closeSessionReporting` outcome — the close outcome it answers from, `childrenClosed` included); `releaseDescendants` is the detach cascade (children detached deepest first with `terminateDebuggee: false`, their rows kept as `stopped` with `programEnd.data.released`, then the parent) 4. **ProxyManager** (`src/proxy/proxy-manager.ts`) - Manages communication with debug proxy process diff --git a/README.md b/README.md index 7eab3e53..36c36a40 100644 --- a/README.md +++ b/README.md @@ -459,7 +459,7 @@ Then get the local variables: - 🔧 [Adapter Development](./docs/architecture/adapter-development-guide.md) – Add new languages - 🔌 [Dynamic Loading Architecture](./docs/architecture/dynamic-loading-architecture.md) – Runtime discovery, lazy loading, caching - 🧩 [Adapter API Reference](./docs/architecture/adapter-api-reference.md) – Adapter, factory, loader, and registry contracts -- 🌳 [Session Tree Design](./docs/architecture/session-tree.md) – A debuggee's child processes as debug sessions (design for issue #1005, not yet shipped) +- 🌳 [Session Tree Design](./docs/architecture/session-tree.md) – A debuggee's child processes as debug sessions (issue #1005; shipped for Python, #1081, and JavaScript, #1082) - 🔄 [CHANGELOG](./CHANGELOG.md) – Release history and upgrade notes - 🐍 [Python Debugging Guide](./docs/python/README.md) – Python-specific features - 💎 [Ruby Debugging Guide](./docs/ruby/README.md) – Ruby debugging with `rdbg`, including remote attach diff --git a/changelog.d/1082-default.changed.md b/changelog.d/1082-default.changed.md new file mode 100644 index 00000000..b81d39a2 --- /dev/null +++ b/changelog.d/1082-default.changed.md @@ -0,0 +1 @@ +**JavaScript child processes are debugged by default on launch and verified-local attach** — a Node launch now sends js-debug `autoAttachChildProcesses: true` unless the caller says otherwise; every attach form, including `processId`, defaults it on only when the inspector target can read both a temporary server-side probe file and the installed js-debug bootloader files. An unverified target defaults it off with a warning, preventing a server-local bootloader path from breaking later forks in a remote or container target; loopback addresses and shared PID namespaces alone do not prove filesystem access. An explicit attach `true` is retained for callers that ensure the target and its children can load the server's js-debug bootloader and reach its debugger endpoint. With child auto-attach enabled, every Node process the program starts (`fork()`, `spawn(process.execPath, …)`, a `cluster` worker, a grandchild) is reported and becomes a child debug session: listed with `parentSessionId`, stopping on the parent's breakpoints, reported by the parent's `wait_for_stop` with `sessionId` naming it. Each child counts against `DEBUG_MCP_MAX_CHILD_SESSIONS` (default 32); beyond the cap a fork is released by a throwaway attach to run undebugged rather than parked (#501). Opt out per launch with `dapLaunchArgs: { autoAttachChildProcesses: false }`, per attach with `adapterConfig: { autoAttachChildProcesses: false }` (the children then run untouched, as before), or server-wide with `DEBUG_MCP_MAX_CHILD_SESSIONS=0`. A launch child's output is on the launch session's buffer (`get_output` answers `outputOn`); an attach root's children keep their own console output (#1005, #1082). diff --git a/changelog.d/1082.added.md b/changelog.d/1082.added.md index 8983606e..937a3dd2 100644 --- a/changelog.d/1082.added.md +++ b/changelog.d/1082.added.md @@ -1 +1 @@ -**JavaScript forks become child debug sessions** — a Node launch with `dapLaunchArgs.autoAttachChildProcesses: true` used to adopt the program itself and release every `fork()`, `spawn(process.execPath, …)` or `cluster` worker to run undebugged (#501); js-debug's report of each is now adopted the way debugpy's is (#1081): every process after the program's own becomes a debug session of its own, listed by `list_debug_sessions` with `parentSessionId`, stopping on the parent's breakpoints (re-sent before it runs), reported by the parent's tree-aware `wait_for_stop` with its `sessionId`, with its own `wait_for_stop` and `close_debug_session`; a grandchild hangs under the fork that started it. What js-debug's measured shapes change: the whole tree's output stays on the launch session's buffer (a child's `get_output` and resource answer `outputOn: ` instead of an empty page), `close_debug_session` on a child releases it to run on (`released: true` — js-debug cannot end a target it adopted), and a child the server does not adopt (the cap, `DEBUG_MCP_MAX_CHILD_SESSIONS`) is released by a throwaway attach rather than a refusal, which would park it forever. The key still defaults to `false`; the default flips in the next step of #1082 (#1005, #1082). +**JavaScript forks become child debug sessions** — a Node launch with `dapLaunchArgs.autoAttachChildProcesses: true` used to adopt the program itself and release every `fork()`, `spawn(process.execPath, …)` or `cluster` worker to run undebugged (#501); js-debug's report of each is now adopted the way debugpy's is (#1081): every process after the program's own becomes a debug session of its own, listed by `list_debug_sessions` with `parentSessionId`, stopping on the parent's breakpoints (re-sent before it runs), reported by the parent's tree-aware `wait_for_stop` with its `sessionId`, with its own `wait_for_stop` and `close_debug_session`; a grandchild hangs under the fork that started it. What js-debug's measured shapes change: the whole tree's output stays on the launch session's buffer (a child's `get_output` and resource answer `outputOn: ` instead of an empty page), `close_debug_session` on a child releases it to run on (`released: true` — js-debug cannot end a target it adopted), and a child the server does not adopt (the cap, `DEBUG_MCP_MAX_CHILD_SESSIONS`) is released by a throwaway attach rather than a refusal, which would park it forever (#1005, #1082). diff --git a/changelog.d/1096-review.fixed.md b/changelog.d/1096-review.fixed.md new file mode 100644 index 00000000..7f2d3195 --- /dev/null +++ b/changelog.d/1096-review.fixed.md @@ -0,0 +1 @@ +**Forwarded JavaScript attaches keep child processes runnable** — the default child-auto-attach setting now checks that the inspected target can read the debugger host's temporary storage and js-debug bootloader files before enabling injection. A localhost SSH or Kubernetes port forward can reach a different filesystem; injecting the host-only preload there made later Node children fail with `MODULE_NOT_FOUND`. If locality cannot be verified, attachment still succeeds with child auto-attach off and a warning. Explicit `adapterConfig.autoAttachChildProcesses` values are preserved; embedders bypassing the adapter transform default off without a locality check. The sibling-session lifecycle test also waits for both children's real breakpoints before closing one, avoiding a race with js-debug's initial pause. diff --git a/docs/README.md b/docs/README.md index 6d2df9bb..fba5e85e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -57,7 +57,7 @@ Start with the [architecture overview](architecture/README.md); it indexes the r [component design](architecture/component-design.md) the per-module detail, and [adapter development](architecture/adapter-development-guide.md) the guide to adding a language. The [session tree](architecture/session-tree.md) is the design for a debuggee's child processes as -sessions of their own (issue #1005 part B; not yet shipped). +sessions of their own (issue #1005 part B: shipped for Python, #1081, and JavaScript, #1082). Cross-cutting patterns are in [`patterns/`](patterns/), and the [logging format](logging-format-specification.md) and [error handling](error-handling-guide.md) specs sit alongside. diff --git a/docs/architecture/session-tree.md b/docs/architecture/session-tree.md index ffa848d5..d504351d 100644 --- a/docs/architecture/session-tree.md +++ b/docs/architecture/session-tree.md @@ -17,11 +17,12 @@ Status: decisions taken 2026-11-03 (listed with their owners at the end); merged cascade answers) in [#1090](https://github.com/debugmcp/mcp-debugger/pull/1090), PR 3 (on by default, the attach measurement, the lifecycle suites) in [#1091](https://github.com/debugmcp/mcp-debugger/pull/1091). M2 -([#1082](https://github.com/debugmcp/mcp-debugger/issues/1082), js-debug) ships in two PRs: the -first lands the mechanism behind an explicit `autoAttachChildProcesses: true` (every report after -the program's own is a child session, the throwaway release, `outputOn`, a child's close as a -release, the fork/grandchild suite and the parent-exit measurement below); the second flips the -launch and attach defaults and adds the attach, cluster and cascade suites. +([#1082](https://github.com/debugmcp/mcp-debugger/issues/1082), js-debug) is complete: PR 1 +(the mechanism behind an explicit `autoAttachChildProcesses: true` — every report after the +program's own is a child session, the throwaway release, `outputOn`, a child's close as a release, +the fork/grandchild suite and the parent-exit measurement below) merged in +[#1094](https://github.com/debugmcp/mcp-debugger/pull/1094), PR 2 (the launch and verified-local +attach defaults on, the attach, cascade and cluster suites) follows it. Scope: debuggee children reported by the adapter (debugpy subprocesses, js-debug forks and, later, browser targets). The tree is additive to the shipped tool surface: no new tool, no changed parameter, new fields only. @@ -227,12 +228,21 @@ Two measured effects the answers name: Adoption is on by default. The Python launch transform stops defaulting `subProcess` to `false` (that default existed only because of the park hang, which adoption removes; the warning that -names the hang goes with it) and the JavaScript launch and attach transforms default -`autoAttachChildProcesses` to `true` (the attach path's `false` was #501's wedge guard, which -adoption replaces; the `attach_to_process` schema text that states it changes with it). An +names the hang goes with it) and the JavaScript launch transform defaults +`autoAttachChildProcesses` to `true`. The JavaScript attach transform defaults it on only for a +verified-local target: every attach form, including `processId`, checks that the inspector +target can read a temporary probe file created by the server and the installed js-debug +`bootloader.js` and `watchdog.js`. An unverified target defaults it off with a warning, because +injecting a server-local bootloader path can break its later forks. A loopback address or shared +PID namespace alone does not establish filesystem access. An explicit attach boolean is retained; +`true` requires the target and its children to be able to load the server's js-debug bootloader +and reach its debugger endpoint. The raw policy fallback stays off when no transformed or +explicit value is available. Adoption replaces #501's wedge guard for local targets; it cannot +make a host-only bootloader available remotely. An explicit `false` in `dapLaunchArgs` turns adoption off for that launch and children run -undebugged, as they did before the tree (an attach's opt-out is the target's own flag, measured -below). Each flip ships in the milestone that +undebugged, as they did before the tree (JavaScript attach uses +`adapterConfig: { autoAttachChildProcesses: false }`; Python attach uses the target's own flag, +measured below). Each flip ships in the milestone that makes it safe (M1 for Python, M2 for js-debug), as a documented behaviour change with a `changed` changelog entry, because it changes what a launch that spawns Python or Node processes shows: child rows for every xdist worker, autoreloader child or script that shells out. @@ -492,17 +502,22 @@ rows, while close removes them. Seams: `src/proxy/minimal-dap.ts` (rows), `packages/shared/src/interfaces/adapter-policy*.ts` (the contract), `packages/adapter-python/src/python-debug-adapter.ts` (the default). -**M2 — js-debug N targets ([#1082](https://github.com/debugmcp/mcp-debugger/issues/1082)).** `ChildSessionManager`'s single-child gate becomes the per-tree cap; +**M2 — js-debug N targets ([#1082](https://github.com/debugmcp/mcp-debugger/issues/1082)), done.** `ChildSessionManager`'s single-child gate became the per-tree cap; every report after the program's own becomes a child session; grandchild reports on child connections are adopted instead of released; #501's throwaway attach stays the release lever and -the only one; `autoAttachChildProcesses` defaults on for launch and attach with a `changed` entry -(the `attach_to_process` schema text changes with it); the attach smoke that -today expects release ([`tests/e2e/mcp-server-smoke-javascript-attach.test.ts`](../../tests/e2e/mcp-server-smoke-javascript-attach.test.ts)) -expects adoption. Reuse M1's subtree-close, idle, attach-target preservation and cleanup-isolation -cases; verify that cap `0` still adopts the program's own target but releases forks (the first PR's -suite does), and measure parent exit with an already adopted, paused child (measured in the first -PR: the child ends with the parent, lifecycle row 2). Unblocks #985 M2 (jest and vitest workers as -sessions). +the only one (PR 1, [#1094](https://github.com/debugmcp/mcp-debugger/pull/1094)). +`autoAttachChildProcesses` defaults on for launch and verified-local attach with a `changed` entry (the +`attach_to_process` schema text changed with it); the attach smoke +([`tests/e2e/mcp-server-smoke-javascript-attach.test.ts`](../../tests/e2e/mcp-server-smoke-javascript-attach.test.ts)) +expects local adoption by default and untouched forks on opt-out; the attach suite +(`javascript-subprocess-attach.test.ts`: the designed flow over `node --inspect`, detach and close as +releases, the opt-out, #501's wedge guard) and the cascade suite (`javascript-subprocess-cascade.test.ts`: +row 1, a sibling's close as a release, a cluster primary's four workers) ride beside the fork/grandchild +suite of PR 1, which also verified that cap `0` still adopts the program's own target but releases +forks and measured parent exit with an already adopted, paused child (the child ends with the parent, +lifecycle row 2). Measured in PR 2: an attach root's children keep their own console output (no +`outputOn`; the attach owns no pipes), and `cluster.fork()` workers are reported like any fork. +Unblocks #985 M2 (jest and vitest workers as sessions). **M3 — browser targets.** After the [#989](https://github.com/debugmcp/mcp-debugger/issues/989) spike: the same mechanism, one session per page, frame and worker; the attach-addressing form is @@ -548,7 +563,9 @@ Taken by the author of this document, marked so they can be overturned: - Detach cascades as a detach (children released); restart closes and removes the previous run's children; neither classifies children as `error`. - The Python attach default flips in M1 only on its own measurement; the js-debug attach default - flips in M2 together with the launch default. + flips in M2 for targets with verified access to the server's temporary and debugger files, + together with the launch default. Unverified targets retain the safe off default unless the + caller explicitly opts in. ## Left to the builder diff --git a/docs/javascript/README.md b/docs/javascript/README.md index 17acee27..ad278f74 100644 --- a/docs/javascript/README.md +++ b/docs/javascript/README.md @@ -206,25 +206,51 @@ to inspect these decisions inside a running mcp-debugger server. ### Child Process Debugging -With `autoAttachChildProcesses: true` every Node process the program starts — a `fork()`, a `spawn(process.execPath, …)`, a `cluster` worker, a grandchild — becomes a child debug session of its own (the [session tree](../architecture/session-tree.md), issue [#1082](https://github.com/debugmcp/mcp-debugger/issues/1082), from the facts measured in [spike-notes-forks.md](spike-notes-forks.md)): listed by `list_debug_sessions` with `parentSessionId`, stopping on the breakpoints set on the parent before launch (re-sent to each child before it runs), reported by the parent's `wait_for_stop` with its `sessionId`. Three things js-debug does differently from debugpy, all answered rather than hidden: the whole tree's output arrives on the launch session's buffer and none of it on a child's — a child's `get_output` answers `outputOn: `; `close_debug_session` on a child releases it to run on (`released: true`) because js-debug cannot end a target it adopted; and a child the server does not adopt (beyond `DEBUG_MCP_MAX_CHILD_SESSIONS`, default 32, `0` off) is released by a throwaway attach-then-detach ([#501](https://github.com/debugmcp/mcp-debugger/issues/501)'s lever), since refusing js-debug's report would park it forever. Function breakpoints do not carry into a child (they are armed over CDP on the program's own target); logpoints do, printing into the launch session's output. The program's exit ends every process under it, a child paused on a breakpoint included (measured): the launch owns the tree, so keep the parent alive while you inspect a child. +Child-process debugging is on by default for a Node launch and a verified-local attach (`autoAttachChildProcesses: true`). With it enabled, every Node process the program starts — a `fork()`, a `spawn(process.execPath, …)`, a `cluster` worker, a grandchild — becomes a child debug session of its own (the [session tree](../architecture/session-tree.md), issue [#1082](https://github.com/debugmcp/mcp-debugger/issues/1082), from the facts measured in [spike-notes-forks.md](spike-notes-forks.md)): listed by `list_debug_sessions` with `parentSessionId`, stopping on the breakpoints set on the parent before launch (re-sent to each child before it runs), reported by the parent's `wait_for_stop` with its `sessionId`. Three things js-debug does differently from debugpy, all answered rather than hidden: the whole tree's output arrives on the launch session's buffer and none of it on a child's — a child's `get_output` answers `outputOn: `; `close_debug_session` on a child releases it to run on (`released: true`) because js-debug cannot end a target it adopted; and a child the server does not adopt (beyond `DEBUG_MCP_MAX_CHILD_SESSIONS`, default 32, `0` off) is released by a throwaway attach-then-detach ([#501](https://github.com/debugmcp/mcp-debugger/issues/501)'s lever), since refusing js-debug's report would park it forever. Function breakpoints do not carry into a child (they are armed over CDP on the program's own target); logpoints do, printing into the launch session's output. The program's exit ends every process under it, a child paused on a breakpoint included (measured): the launch owns the tree, so keep the parent alive while you inspect a child. + +To choose the default, every attach form, including `processId`, verifies that the inspector +target can read both a temporary probe file created by the server and the installed js-debug bootloader files +(`bootloader.js` and `watchdog.js`). A loopback address or shared PID namespace alone is not +enough: `localhost` can be an SSH or `kubectl` port forward, and a sidecar can share PIDs without +sharing files. When that access cannot be verified, including a failed probe, child auto-attach +defaults to `false` and the attach response warns. This keeps +js-debug from injecting a server-local bootloader path into a remote or container target, which +can make later forks fail to start. An explicit `adapterConfig: { autoAttachChildProcesses: true }` +still enables it; use that override only when the target and its children can load the server's +js-debug bootloader and reach its debugger endpoint. For an `attach_to_process` root, the default console capture delivers each child's console output to that child's own buffer and resource; it does not return an `outputOn` pointer to the root. A launch child's output resource still includes any diagnostics recorded for that child -alongside the pointer to the launch output. +alongside the pointer to the launch output. One hazard of an attached target started with +`node --inspect`: a child it `fork()`s inherits that flag through `execArgv` by default, fights +the parent for the inspector port, and js-debug's process-tree scan then attaches it as a +nameless `Child Process []` target whose breakpoints never bind, racing the bootloader's own +report (seen on a slow CI runner). Fork with `execArgv: []` in a program that is attached to this +way; the bootloader reaches the child through `NODE_OPTIONS` regardless. And the bootloader is in +the attached process only a moment *after* the attach answers: js-debug sets `NODE_OPTIONS` and +`VSCODE_INSPECTOR_OPTIONS` in its environment over the inspector, with retries, so a child forked +inside that window runs unreported (measured on a slow CI runner). A program that forks on a +trigger right after being attached to with child auto-attach enabled should wait for +`process.env.VSCODE_INSPECTOR_OPTIONS` to appear, as the attach fixture does. Fork logpoints use a `console.log` fallback because each fork does not have its own CDP logpoint bridge. A child that replaces `console.log` can suppress those messages; the program target's CDP-backed logpoints are unaffected. -The key still defaults to `false` (the default flips in M2's second PR); pass it explicitly in `dapLaunchArgs`: +Opt out per launch with `dapLaunchArgs: { autoAttachChildProcesses: false }`, per attach with +`adapterConfig: { autoAttachChildProcesses: false }`, or server-wide with +`DEBUG_MCP_MAX_CHILD_SESSIONS=0` (the program's own target is still adopted; every fork is released +to run undebugged). Opted out, the children run as they did before the session tree: untouched by the +debugger, their output on the parent's buffer where the parent's pipes carry it. ```javascript // parent.js const { fork } = require('child_process'); const child = fork('child.js'); -// child.js becomes a child debug session when autoAttachChildProcesses is true +// child.js is a child debug session of parent.js's: set its breakpoints on the +// parent's session before launch, then wait_for_stop on the parent reports them ``` ### Conditional Breakpoints diff --git a/docs/tool-reference.md b/docs/tool-reference.md index fed5bd04..daa4ead0 100644 --- a/docs/tool-reference.md +++ b/docs/tool-reference.md @@ -131,7 +131,7 @@ Lists all active debugging sessions — in Streamable HTTP mode, the ones this M Errored sessions include optional `diagnostics` with the current launch attempt's server-host `proxyLogPath` and remote-safe `proxyLogResource`. The record is retained for proxy initialization failures and for proxy/adapter deaths after initialization, and is cleared when a new launch or attach attempt begins. -**`parentSessionId`:** present on child rows only. A child session is created by the server, never by `create_debug_session`, when the adapter reports a process the debuggee started: a Python launch or attach — debugpy's sub-process debugging is on by default; `dapLaunchArgs: { subProcess: false }` opts a launch out and an attach target decides for itself with `--configure-subProcess` — where debugpy reports each child (see [the session tree](./architecture/session-tree.md)). The child inherits the parent's `language`, its `name` is the adapter's own label for the process (debugpy: `Subprocess `), it has its own breakpoints, `get_output` and `close_debug_session`, and it carries no `exitCode` (it ends with the adapter's `terminated` event). The parent's [`wait_for_stop`](#wait_for_stop) reports the child's stop with `sessionId` naming the child and `children` listing the tree; a child can still be waited on by its own id (that wait answers for the child and its own descendants only). `start_debugging`, `restart_debugging`, `attach_to_process`, `detach_from_process`, `expose_session` and `unexpose_session` refuse a child id by name. Closing the parent closes its children first (`childrenClosed` in the answer). `DEBUG_MCP_MAX_CHILD_SESSIONS` caps the live children of one tree (default 32; `0` turns adoption off): a child reported beyond the cap is released and runs undebugged, and the parent's next `wait_for_stop`, `start_debugging` or `restart_debugging` answer counts the run's released children in `childrenReleased`. +**`parentSessionId`:** present on child rows only. A child session is created by the server, never by `create_debug_session`, when the adapter reports a process the debuggee started: a Python launch or attach — debugpy's sub-process debugging is on by default; `dapLaunchArgs: { subProcess: false }` opts a launch out and an attach target decides for itself with `--configure-subProcess` — where debugpy reports each child, or a JavaScript launch or verified-local attach — js-debug's `autoAttachChildProcesses` is on by default for launch or an inspector target that can read both a temporary server-side probe file and the installed js-debug bootloader files; other attaches default it off with a warning; `dapLaunchArgs: { autoAttachChildProcesses: false }` opts a launch out, `adapterConfig: { autoAttachChildProcesses: false }` an attach — where js-debug reports every Node process the program forks or spawns, `cluster` workers and grandchildren included (see [the session tree](./architecture/session-tree.md)). The child inherits the parent's `language`, its `name` is the adapter's own label for the process (debugpy: `Subprocess `; js-debug: `