fix(launch): a noDebug launch says what it did to the debugger instead of blaming the breakpoints (#710) - #747
Merged
Conversation
#710) dapLaunchArgs.noDebug is a standard DAP flag every adapter honours by not enabling the debugger (JavaScript included since #706 forwards unknown launch keys): no breakpoint binds and no stop ever arrives. The session then read as a mystery — a short script ended `stopped` with the #467 "check the file path and line" warning for breakpoints that were never going to bind; a server stayed `running` with no warning at all. - launch-warnings.ts: buildNoDebugLaunchWarning, a pure builder over the breakpoint stores, the launch args and the caller's explicit breakOnExceptions. Fires only when the caller asked for a stop (line or function breakpoints, explicit breakOnExceptions other than 'none', stopOnEntry); the policy default does not count, so a deliberate plain run stays silent. - debug-launcher.ts: decided once per launch, before the dry-run branch, so a dryRunSpawn configuration check and a restart_debugging replay carry it too (restart bypasses handler intake, which is why this lives in the launcher). When it fires, the #308/#467/#469 breakpoint-shaped warnings are withheld — each diagnoses a symptom of this one cause. - tool-schemas.ts + docs/tool-reference.md: document the flag; the tool-list snapshot fence re-recorded for the new property. - Tests: builder + launcher/dry-run/restart wiring (mock proxy), and an e2e against real js-debug asserting the warning replaces the #467 text. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This was referenced Sep 16, 2026
…lity; say "no effect" where the flag is ignored (#710) Review of #747: the issue's premise that every adapter honours noDebug was wrong, and the first version of this PR carried it into a warning that was false for half the languages while suppressing the accurate diagnostics. Measured per adapter (set a breakpoint, launch with noDebug): honours it: js-debug (runs, no stop); debugpy, Delve, CodeLLDB (they honour it too, but the launch currently fails under it — #746) ignores it: rdbg, netcoredbg, the JDI bridge, the mock adapter never sees it: rust (transformLaunchConfig builds from a fixed key set) - AdapterPolicy.honoursNoDebug (pinned per language in the policy contract test): true for javascript/python/go/cpp, unset elsewhere. - The launcher resolves noDebug the way the adapter will see it (adapterLaunchConfig over dapLaunchArgs over the server defaults — the proxy launcher's merge order) and decides debuggerOff = noDebug && honoursNoDebug. Only that gates the #308/#467/#469 suppression. - Where the adapter ignores the flag the warning says so ("noDebug has no effect with the ruby adapter: the debugger stays on") and the usual diagnostics stay. - With the debugger off, readiness no longer waits for an entry stop that cannot come (stopOnEntry + noDebug used to sit on the 30 s ceiling), and the session is projected RUNNING instead of staying INITIALIZING. - Builder takes ExceptionBreakMode; tests build real Breakpoint maps; the two dry-run payloads collapse into one helper; setMockProxyRunning moves to session-manager-test-utils (was copied in two suites). - Docs, schema and fragment state which adapters do what; e2e adds the mock (ignoring) case beside the js (honouring) one. Follow-ups: #746 (go/cpp findings added), #749 (downstream surfaces). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…verything that reads it (#710) Second review of #747, on the readiness handling added last round: - The readiness-only stopOnEntry view was inert for python/go/cpp, whose isSessionReady is `state === PAUSED` (they always request an entry stop and auto-continue), and racy for js-debug, whose adapter-configured fires before the readiness listener exists while the core still withheld RUNNING under the caller's stopOnEntry; the post-hoc RUNNING projection then stamped a state nothing had observed. Replaced at the root: when debuggerOff, the launch args handed to the proxy, the handshake and the readiness wait carry stopOnEntry:false, so the core's projection, the readiness predicate and the adapter agree; the readiness policy drops its pause-only predicate (running is ready). session.lastLaunch and the warning keep the caller's value. Both band-aids are gone. - noDebug and stopOnEntry are resolved the way the adapter sees them (adapterLaunchConfig over dapLaunchArgs [over defaults for noDebug]) and by truthiness — the proxy forwards a string 'true' verbatim and js-debug gates on truthiness. - A failed launch under the flag (debugpy/Delve/CodeLLDB today, #746) carries the noDebug note in its data instead of an init failure with no pointer to the flag behind it. - A stop that lands before the launch reports outranks the policy pin: the ordinary diagnostics stay and the note says the flag had no effect. - Logpoints are counted apart from breakpoints, as the run-to-completion summary does; the redundant second policy lookup reads the hoisted one; overridePolicy's doc comment is back on overridePolicy and says which seam it targets. - Tests: adapter-configured is emitted synchronously inside start() (the real ordering); a pause-only readiness policy; stopOnEntry via adapterLaunchConfig; string-typed noDebug; the failed-launch note; the stop-that-arrived-anyway guard; logpoint counting. The two readiness tests fail with the root fix disabled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- A pause that came anyway counts as ready under debuggerOff (it fell to RUNNING-only and sat on the 30 s ceiling); an entry stop the core already resumed counts as evidence too (firstStopHandled is set on every stop of the launch), so a wrong policy pin plus stopOnEntry no longer reports "no stop can come" after one fired. - stopOnEntry is neutralized in adapterLaunchConfig as well as dapLaunchArgs — adapterLaunchConfig wins the adapter merge, so the worker and the js child adoption would still have waited on it. - The #295 worker force (stopOnEntry=true for pending CDP function breakpoints) yields to an honoured noDebug: the breakpoints cannot bind and the entry stop cannot come. - Attach-shaped start_debugging is left alone: noDebug is a launch request property. - A bare noDebug launch that fails under the flag carries a note too (the warning had nothing armed to say), and every failure return uses failureData — the dry-run timeout and the teardown branch dropped it. - resolveLaunchFlag reads 'true'/'false' the way the proxy parser coerces them and drops the defaults source (the server defaults are typed stopOnEntry/justMyCode only); LaunchContext no longer needs defaultDapLaunchArgs. The texts name the flag alone — either source may have carried it. Readiness is one explicit predicate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… cannot know (#710) "the adapter accepted the flag but the launch did not complete under it" asserted causation on any failure under an honoured noDebug, including a bad scriptPath. The note now says the debugger was off and offers the remedy: drop the flag if the failure is unexpected, since some adapters cannot complete a launch under it (#746). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
debugmcpdev
pushed a commit
that referenced
this pull request
Sep 17, 2026
…t reason, a hit, an exception or an entry — and the launch warning stops claiming no stop can arrive (#749 review 3) Third pass on #751 (twelve findings; eleven taken, one declined below): - js-debug relabels a `debugger;` statement stop ('pause', "Paused on debugger statement") to 'breakpoint', and under noDebug that pause lands (measured) — so the clear must key on what the adapter itself reported: stopProvesDebuggerOn() = hitBreakpointIds present, or an exception or entry stop, or a breakpoint reason the adapter called one (raw and normalized). An uncaught throw under js noDebug was measured not to stop, so 'exception' stays as proof. - The restored PAUSED clause in stoppedAnyway made the launch response say "no effect … breakpoints work as usual" while the record said they cannot bind. The root was #747's wording — "the debugger is disabled … and no stop can arrive" — refuted for js by a pause, a step and a `debugger;` statement. The warning now says the debugger is off for this launch and names what will not fire, so the record and the response agree, and the PAUSED clause goes again. - The raw record is now `launchDebuggerOff` on ManagedSession, distinct from the projected `debuggerDisabled` on DebugSessionInfo, so no handler can read the ungated value by accident. - isDebuggerOff() includes INITIALIZING: the proxy is up and a breakpoint set in that window still goes to the adapter, whose "Unbound breakpoint" needs the why. - A paused session gets only the clause still true of it — breakpoints cannot bind — not "no stop is expected". - The debugger-off pending-pause message keeps the policy's own explanation: on js-debug the pause can land under the flag, and #678's smart-stepper advice is what makes it. - withDebuggerOffWhy no longer mutates the adapter's error: a new Error with the why, the original untouched as the cause. - The composed "not paused" texts live in error-messages.ts (notPaused, cannotEvaluateNotPaused, stackTraceNotPaused, noStackFramesNotPaused, withDebuggerOffWhy, pausePendingDebuggerOff); one debuggerOffWhyFor(ctx, id) in handlers/shared.ts replaces three ctx→session→why copies; the USER_BREAK_REASONS re-export shim is gone. - Declined: treating an auto-continued 'pause'-reason entry stop as proof the flag was ignored (a stale-pin js-debug build stopping at entry as 'pause' while the launcher neutralised stopOnEntry) — the record self-corrects on the first breakpoint the adapter reports hit, and reading a pause as proof is the bug the round fixed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
debugmcpdev
added a commit
that referenced
this pull request
Sep 17, 2026
…so on every later surface (#749) (#751) * fix(session): a session whose launch runs with the debugger off says so on every later surface (#749) #747 decided "this noDebug launch runs with the debugger off" once, for the start_debugging response, and forgot it. Nothing on the session recorded it, so set_breakpoint on the running session came back verified:false with no reason, list_breakpoints showed everything unbound with none, pause_execution ended in the #678 "may be blocked in native code" guess, and stepping, get_stack_trace, get_local_variables and evaluate_expression answered "not paused" in debugger terms. Record: `DebugSessionInfo.debuggerDisabled` (inherited by ManagedSession), written in the launcher where debuggerOff is decided (real launches, not dry runs), reset in the launcher's and the attach controller's per-attempt blocks — not in the core's setupProxyEventHandlers, which runs inside proxyLauncher.start after the launcher wrote it — and cleared in handleStopped: a real stop is stronger evidence than the policy's pin. Projected by SessionStore.getAll() and list_debug_sessions. Consult, never pre-empting the adapter: every request still goes to the adapter and its own answer is kept; one sentence (ErrorMessages.debuggerOffForLaunch) is added beside it — set_breakpoint (unverified only), list_breakpoints (top-level warning), pause pending (instead of the policy's guess) and pause refused (appended), "Not paused:" on step/continue, and the stack-trace note, evaluate error and locals message. Measured: js-debug still lands a pause under noDebug, so the sentence says "no stop is expected" rather than "cannot come", and that stop clears the flag. Tests: field lifecycle in session-manager-nodebug-warning.test.ts, both projections, every consumer, and a js e2e over pause_test.js (run locally — CI does not run the e2e project). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(session): a pause does not clear the debugger-off decision; one gated why for every surface (#749 review) Review of #751 (nine findings, all taken): - handleStopped cleared debuggerDisabled on ANY stop, but the PR's own measurement says js-debug lands a user pause under noDebug with its breakpoints still off — so after the first pause every surface lost its why again. Only a stop a live debugger produces clears it now (DEBUGGER_ON_STOP_REASONS: the breakpoint family, exception, entry, step); the launcher's stoppedAnyway reads the same record instead of its own firstStopHandled/PAUSED discriminator, so the launch response and the record cannot disagree. - The record was never cleared on STOPPED/ERROR, so a breakpoint queued after the run said "cannot bind" and list_debug_sessions kept reporting a stopped session as debugger-off. isDebuggerOff() gates the decision on the session being running or paused; the projection and every consumer read through it. - One home for the concern: src/session/debugger-off.ts (the reason set, isDebuggerOff, debuggerOffWhy) replaces five hand-spliced reads. - The pending-pause message appended "no stop is expected" to a base text that promises one; ErrorMessages.pausePendingDebuggerOff is one message. - A refused pause threw a fresh Error, dropping the adapter's own error object (stack, props); the original is rethrown with the why appended, and the no-debug-target return carries the why too. - list_breakpoints warned even when every record was verified, contradicting set_breakpoint's own gate; now only with an unverified record. - The e2e's refusal branch was unreachable (callTool throws on an MCP error); callToolSafely, and the landed-pause branch asserts the decision is kept, as measured. - docs: list_breakpoints documents the top-level warning; the noDebug paragraph, the list_debug_sessions field note and the fragment describe which stops clear the decision and that it is not reported once the session is over. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(session): a step from a noDebug pause proves nothing either; the decision is consulted only while running or paused (#749 review 2) Re-review of #751 (eight findings, all taken): - 'step' was in the set of stops that clear the decision, but the js pause the PR measured lands because the inspector is attached, and so does a step taken from it (measured: reason 'step', breakpoints still unbound) — one step_over after the pause put every surface back in the pre-#749 state. The set is now the user-asked stops (breakpoint family, exception) plus 'entry'; the e2e steps after the pause and checks the record survives. - A launch refused before the proxy existed (the MSVC-toolchain branch) moves the session back to CREATED with the record intact, and the gate only excluded terminal states. isDebuggerOff() now means running or paused, exactly as the docs said. - stoppedAnyway had dropped the PAUSED clause, so a launch ending paused on a 'pause' stop could say no stop can arrive. Restored: a paused launch never claims that, whatever the record says about breakpoints. - withDebuggerOffWhy appends to the adapter's own error; when the message will not take the append (getter-only), it wraps with the original as the cause instead of throwing from inside the pause path. - The tool-reference bullet said "no stop ever arrives" and, four sentences on, that js-debug lands a pause: "no breakpoint, exception or entry stop ever arrives". - The e2e comment said the pause makes the session forget the decision while the assertion checked the opposite; the comment matches now. - handleListDebugSessions re-gated a field SessionStore.getAll() had already gated; it mirrors the field. - USER_BREAK_REASONS moved next to BREAKPOINT_STOP_REASONS in @debugmcp/shared (the core re-exports it); DEBUGGER_ON_STOP_REASONS is that set plus 'entry' rather than a third hand-built union. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(session): proof the debugger is on is the adapter's own breakpoint reason, a hit, an exception or an entry — and the launch warning stops claiming no stop can arrive (#749 review 3) Third pass on #751 (twelve findings; eleven taken, one declined below): - js-debug relabels a `debugger;` statement stop ('pause', "Paused on debugger statement") to 'breakpoint', and under noDebug that pause lands (measured) — so the clear must key on what the adapter itself reported: stopProvesDebuggerOn() = hitBreakpointIds present, or an exception or entry stop, or a breakpoint reason the adapter called one (raw and normalized). An uncaught throw under js noDebug was measured not to stop, so 'exception' stays as proof. - The restored PAUSED clause in stoppedAnyway made the launch response say "no effect … breakpoints work as usual" while the record said they cannot bind. The root was #747's wording — "the debugger is disabled … and no stop can arrive" — refuted for js by a pause, a step and a `debugger;` statement. The warning now says the debugger is off for this launch and names what will not fire, so the record and the response agree, and the PAUSED clause goes again. - The raw record is now `launchDebuggerOff` on ManagedSession, distinct from the projected `debuggerDisabled` on DebugSessionInfo, so no handler can read the ungated value by accident. - isDebuggerOff() includes INITIALIZING: the proxy is up and a breakpoint set in that window still goes to the adapter, whose "Unbound breakpoint" needs the why. - A paused session gets only the clause still true of it — breakpoints cannot bind — not "no stop is expected". - The debugger-off pending-pause message keeps the policy's own explanation: on js-debug the pause can land under the flag, and #678's smart-stepper advice is what makes it. - withDebuggerOffWhy no longer mutates the adapter's error: a new Error with the why, the original untouched as the cause. - The composed "not paused" texts live in error-messages.ts (notPaused, cannotEvaluateNotPaused, stackTraceNotPaused, noStackFramesNotPaused, withDebuggerOffWhy, pausePendingDebuggerOff); one debuggerOffWhyFor(ctx, id) in handlers/shared.ts replaces three ctx→session→why copies; the USER_BREAK_REASONS re-export shim is gone. - Declined: treating an auto-continued 'pause'-reason entry stop as proof the flag was ignored (a stale-pin js-debug build stopping at entry as 'pause' while the launcher neutralised stopOnEntry) — the record self-corrects on the first breakpoint the adapter reports hit, and reading a pause as proof is the bug the round fixed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(schema): the dapLaunchArgs.noDebug description stops claiming no stop ever arrives (#749) The schema is the one noDebug text the model reads before choosing the flag, and it still said "no stop ever arrives" after #751 retired that claim in the warning, the fragment and the docs: js-debug lands a user pause under the flag. It now says what is true of every honouring adapter — no breakpoint, exception or entry stop — and that the session reports debuggerDisabled while such a launch runs. Fences re-recorded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(server): get_stack_trace on a running session with no thread answers not-paused with the why, not "no active proxy" (#749) Measured on the merged tree (#750 + #751) with debugpy under noDebug: nothing ever stops, so no thread is current, and debugpy refuses the `threads` discovery ("Server is not available") — the facade then threw ProxyNotRunningError, "Cannot get stack trace: no active proxy", for a session whose proxy was alive. js-debug hid this: its inspector answers `threads`, so the js e2e reached the resolver's not-paused note. On a session that is not paused, no thread is expected; the session layer's not-paused answer (with the debugger-off why) needs none, so the facade hands it there. A paused session with no thread to name keeps the error — that is the anomaly it describes. The two existing throw tests now say "paused". The python surfaces e2e (the clean case: debugpy attaches no debugger at all) covers set_breakpoint, list_breakpoints, get_stack_trace, get_local_variables, evaluate_expression, step_over and pause_execution; watched it fail on exactly this answer against a dist without the fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(session): a verified breakpoint is proof the debugger is on; get_stack_trace never claims "no active proxy"; pause on an initializing launch says why (#749 review 4) Fourth pass, on the merged tree (#750 + #751), seven findings, all taken: - get_stack_trace asked the adapter for `threads` before the state check that made the answer irrelevant: a session that is not paused gets the session layer's not-paused note (with the why) with no round trip — on a wedged adapter that request blocked for the DAP timeout first. - The PAUSED + no-thread branch still threw "no active proxy" for a proxy that was alive; the resolver's "No stopped thread is known for this session." is the truthful answer, so the throw is gone (the error stays for a session with no proxy at all). - A breakpoint the adapter verified is proof this build debugs after all, and it arrives before any hit. Measured first: every honouring adapter refuses or unbinds a live breakpoint under the flag (js-debug "Unbound breakpoint", debugpy "Server is not available", Delve "noDebug mode: unable to process 'setBreakpoints'", CodeLLDB "Not supported in noDebug mode"), so a verified record can only come from a build that ignores it. Consulted on read (adapterVerifiedABreakpoint, inside isDebuggerOff) since bindings are per-launch state; the launch response reads the same evidence, so "will not fire" is never said of a breakpoint list_breakpoints shows bound. - pause_execution on an INITIALIZING debugger-off session was the one surface without the why. - withDebuggerOffWhy now passes the rejected value itself as the cause, not a synthesized Error, when the bridge threw a non-Error. - The "Used in" map on debuggerOffForLaunch named five files that never reference it; it names the one gate that does. - The list_breakpoints doc note quotes the paused variant too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: JF <john.franklin@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Closes #710.
What
dapLaunchArgs.noDebug: trueis DAP's "launch without enabling debugging". Where the adapter honours it, no breakpoint binds, no exception filter arms, and no stop ever arrives — and the session read as a mystery: a short script endedstoppedcarrying the #467 warning ("check the file path and line") for breakpoints that were never going to bind; a server stayedrunningwith no warning while the caller waited for a stop that could not come.start_debuggingnow says what the flag did. Where the debugger is off and the caller asked for a stop:Where the adapter ignores the flag:
The premise was wrong, so it is now measured
The issue (and the first version of this PR) assumed every adapter honours the flag. The
/code-reviewon the first commit checked the sources, and a live probe of every adapter (set a breakpoint, launch with{ stopOnEntry: false, noDebug: true }) gave the full picture:stopped, exit 0initialized, init failure — #746server_dap.rbnever reads it)JdiDapServer.javanever reads it)So the first version's warning was false for half the languages — and worse, it suppressed the accurate #467/#308/#469 diagnostics for them. The redesign:
AdapterPolicy.honoursNoDebug(beside thefunctionBreakpointsBindLateprecedent), true for javascript/python/go/cpp and unset elsewhere, pinned per language intests/unit/shared/adapter-policy-contract.test.tsso a policy cannot start or stop claiming it silently. The fourtrues carry a comment saying what was observed.noDebugthe way the adapter will see it —adapterLaunchConfigoverdapLaunchArgsover the server defaults, the proxy launcher's merge order — and decidesdebuggerOff = noDebug && honoursNoDebug. Only that gates the suppression of the breakpoint-shaped warnings.Design decisions kept from the first round
restart_debuggingreplayssession.lastLaunchthrough the privateDebugLauncher.launch()and never touchesnormalizeStartDebuggingArgs, so an intake-only warning could never fire on the restart where breakpoints were added after the launch. In the launcher the breakpoint stores, the caller'sbreakOnExceptions, the launch config and the policy are all in hand, anddata.warningalready lifts into the top-levelwarningfor start and restart.dryRunSpawncarries it too — a configuration check should say so.breakOnExceptionsother thannone, orstopOnEntry. The effective exception mode is deliberately not used — the policy default isuncaughtalmost everywhere and would make every barenoDebugrun warn. (defaultDapLaunchArgsmerges later, sostopOnEntryat this seam is the caller's value.)Also from the reviews (two rounds)
stopOnEntryis neutralized once, for everything that reads it.stopOnEntry: true+noDebug: trueused to sit on the 30 s readiness ceiling. The first fix (a readiness-only view plus a post-hoc RUNNING projection) was shown to be inert for python/go/cpp — theirisSessionReadyisstate === PAUSED, they always request an entry stop and auto-continue — and racy for js-debug, whoseadapter-configuredfires before the readiness listener exists while the core still withheld RUNNING under the caller's value. Now the launch args handed to the proxy, the handshake and the readiness wait carrystopOnEntry: false, the readiness policy drops its pause-only predicate (running is ready), and the core's own projection does the rest;session.lastLaunchand the warning keep the caller's value. Both band-aids are gone.noDebugandstopOnEntryare resolved the way the adapter sees them (adapterLaunchConfigoverdapLaunchArgs, over the server defaults fornoDebug) and by truthiness — the proxy forwards a string'true'verbatim and js-debug gates on truthiness.initializedevent) and error on cpp (CodeLLDB refuses configuration in noDebug mode) #746) carries a noDebug note in itsdata, so the caller sees the flag before retrying paths and ports. The note states the fact and the remedy ("the debugger was off; if the failure is unexpected, drop noDebug") without asserting the flag caused a failure it cannot attribute — a badscriptPathundernoDebugis still a bad path.ExceptionBreakMode; tests build realBreakpointmaps; the two dry-run payloads collapse into one helper;setMockProxyRunningmoves tosession-manager-test-utils.ts; the redundant second policy lookup reads the hoisted one.firstStopHandled);stopOnEntryis neutralized inadapterLaunchConfigtoo (it wins the adapter merge — the worker and the js child adoption would still have waited on it); the worker's js-debug function breakpoints via the CDP proxy (revives #282 by another route) #295stopOnEntryforce for pending CDP function breakpoints yields to an honourednoDebug; attach-shapedstart_debuggingis left alone (noDebugis a launch-request property); a barenoDebuglaunch that fails carries a note even with nothing armed, and every failure return carries it;'true'/'false'strings are read the way the proxy parser coerces them; the server-defaults source is dropped (they are typedstopOnEntry/justMyCodeonly); the texts name the flag alone since either source may have carried it.set_breakpoint,list_breakpoints,pause_executionon anoDebugsession) is the right next step but a separate change — noDebug sessions: set_breakpoint, list_breakpoints, pause and inspection still explain themselves in debugger terms after a debugger-off launch #749.breakpointsReappliedon restart keeps its documented meaning (breakpoints re-sent); the warning beside it says they will not fire.Changes
packages/shared/src/interfaces/adapter-policy.ts+ the js/python/go/cpp policies —honoursNoDebug.src/session/breakpoints/launch-warnings.ts—buildNoDebugLaunchWarning, pure, two texts.src/session/launch/debug-launcher.ts—resolveLaunchFlag, the policy-gated decision, the neutralizedlaunchArgs/readinessPolicywhen the debugger is off,failureData,dryRunResult;operations-context.tsgives the launch slicedefaultDapLaunchArgs.src/server/tool-schemas.ts(+ the 24 tool-list snapshots),docs/tool-reference.md,changelog.d/710.fixed.md— say which adapters do what.adapterLaunchConfigas the source and as the override, readiness with the real event order and with a pause-only policy, string-typed flag, failed launch, a stop that arrived anyway, dry run, restart, string forms, attach-shaped, bare failed launch, resumed entry stop — 18), the policy contract pin, and two e2e cases intests/e2e/mcp-server-break-on-exceptions.test.ts: js-debug (honours:success, warning, no Breakpoint-binding failures are under-surfaced: start_debugging hides unbound breakpoints, and known-never-bind function breakpoints are still accepted #467 text,stopped/exit 0 — fails on main's build with exactly the issue's symptom) and the mock adapter (ignores: "no effect", stillpausedat the breakpoint).Verification
lint,typecheck:all,changelog:check,check:docsclean;tests/core/unit/session+tests/core/unit/server+tests/unit/shared: 102 files / 1840 tests (session, server, shared, proxy). CI does not run the e2e project, and the worker change sits on the js function-breakpoint path only e2e drives, sotests/e2e/mcp-server-smoke-js-function-bp.test.ts+ the fulltests/e2e/mcp-server-break-on-exceptions.test.ts(26 tests) were run locally against the rebuiltdist— green; the readiness and merged-source wiring tests each fail when their fix is disabled; pre-push gate passed.Follow-ups filed: #746 (python/go/cpp launches fail under
noDebug— matrix attached), #749 (downstream surfaces).🤖 Generated with Claude Code