Skip to content

feat: break-on-exception support (breakOnExceptions) + exception detail + debuggee exit code (#220) - #245

Merged
debugmcpdev merged 2 commits into
mainfrom
feat/220-break-on-exceptions
Aug 3, 2026
Merged

feat: break-on-exception support (breakOnExceptions) + exception detail + debuggee exit code (#220)#245
debugmcpdev merged 2 commits into
mainfrom
feat/220-break-on-exceptions

Conversation

@debugmcpdev

Copy link
Copy Markdown
Collaborator

Fixes #220.

What

An uncaught exception in a debuggee previously terminated the session — indistinguishable from a clean exit, with stack and locals gone. This PR adds the missing tool surface and the two related visibility gaps called out in the issue:

  • breakOnExceptions: "uncaught" | "all" | "none" (default "none", opt-in — zero behavior change when unset) on start_debugging and attach_to_process. The abstract mode is resolved to concrete DAP exceptionBreakpointFilters IDs per language by the adapter policy (getInitializationBehavior().exceptionFilters + exported resolveExceptionFilters()):

    Language uncaught all
    Python uncaught raised, uncaught
    JavaScript uncaught all (runtime-verified against js-debug)
    Java uncaught caught, uncaught
    .NET user-unhandled all
    Go fatal, panic fatal, panic
    Rust rust_panic rust_panic, cpp_throw
    Ruby — (warn + skip; rdbg has no uncaught-only filter) any
  • Exception detail on stops: lastStop now records the stopped event's description/text (exception class and message) — surfaced via list_debug_sessions, get_stack_trace, and the start_debugging response.

  • Debuggee exit code: the DAP exited event's exitCode is stored on the session and returned in list_debug_sessions, so a crash (non-zero) is distinguishable from a clean exit.

How

  • The worker sends setExceptionBreakpoints in handleInitializedEvent between initial breakpoints and configurationDone — one site covering python launch-first, go/java/ruby/dotnet sendLaunchBeforeConfig, and both attach variants. Failures are warn-logged and never abort the launch (inner try/catch; the outer catch calls shutdown()).
  • js-debug (command-queueing) path: the parent handshake, child-session bootstrap, and post-attach replay all send the same resolved filters (the mode travels worker → MinimalDapClient.setExceptionBreakModeChildSessionManager). Unset mode resolves to [] — byte-identical to the previous hardcoded {filters: []}.
  • The mode travels the init IPC as an abstract value; policies live in @debugmcp/shared, shared by both processes, so the mapping cannot drift.
  • Mock adapter: new setExceptionBreakpoints handler + double-gated exception simulation (filters armed AND program path matches /throws|error/i), and it now emits exited before terminated (matching debugpy — the SessionManager tears down handlers on terminated).

Verified

  • Unit: 2584 tests / 158 files green, including new coverage: table-driven filter-mapping guardrail (all 8 policies × 4 modes), worker ordering/failure-tolerance/unsupported-mode tests, init-command threading (the explicit-field-copy silent-drop hazard), message-parser validation, lastStop detail + exitCode, child-session filter threading.
  • E2E (new tests/e2e/mcp-server-break-on-exceptions.test.ts, 7 tests green): mock full-stack (pause → continue → exitCode: 1), python launch pausing at the ZeroDivisionError crash site with locals a=10, b=0 live + no-option regression guard (terminates, non-zero exitCode), javascript pause-at-uncaught, python attach with filters armed during the attach init sequence (new attach_then_raise.py fixture).
  • Manual MCP flow via dev-proxy: crash-site pause, locals, lastStop detail, exitCode: 1 after continue.
  • Runtime-verified js-debug filter IDs from its initialize response (all/uncaught) and corrected the drifted static declaration in adapter-javascript.

Notes

🤖 Generated with Claude Code

cynarlab and others added 2 commits August 3, 2026 18:57
…il + exit code (#220)

- breakOnExceptions ('uncaught'|'all'|'none', default none) on start_debugging
  and attach_to_process; abstract mode resolved to per-language DAP filter IDs
  via AdapterPolicy.getInitializationBehavior().exceptionFilters
- worker sends setExceptionBreakpoints between initial breakpoints and
  configurationDone (all launch/attach variants); failures never abort launch
- js-debug path: parent handshake + child sessions + post-attach replay arm
  the same resolved filters (setExceptionBreakMode threading)
- lastStop now records the stopped event's description/text (exception
  class/message)
- debuggee exit code from the DAP exited event surfaced on the session and in
  list_debug_sessions
- mock adapter: setExceptionBreakpoints handler + gated exception simulation;
  emits exited before terminated (matches debugpy ordering)
- runtime-verified js-debug filter IDs ('all'/'uncaught'); corrected the
  static capability declaration

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…220 limitation notes

Also: replace the js no-option e2e with a pointer to pre-existing launch-race
issue #242 (reproduced on main); align the js capabilities test with the
runtime-verified filter IDs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.15385% with 9 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/proxy/dap-proxy-connection-manager.ts 0.00% 5 Missing ⚠️
src/proxy/minimal-dap.ts 0.00% 2 Missing ⚠️
src/proxy/child-session-manager.ts 85.71% 1 Missing ⚠️
src/server.ts 87.50% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@debugmcpdev
debugmcpdev merged commit 9583adc into main Aug 3, 2026
9 of 10 checks passed
@debugmcpdev
debugmcpdev deleted the feat/220-break-on-exceptions branch August 3, 2026 23:11
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.

[FEATURE] No way to enable break-on-exception — uncaught exceptions terminate instead of pausing

2 participants