Skip to content

fix: report pending success instead of failing when step/pause outlives grace window#144

Merged
debugmcpdev merged 3 commits into
mainfrom
fix/step-pause-grace-window
Jul 7, 2026
Merged

fix: report pending success instead of failing when step/pause outlives grace window#144
debugmcpdev merged 3 commits into
mainfrom
fix/step-pause-grace-window

Conversation

@debugmcpdev

Copy link
Copy Markdown
Collaborator

Problem

step_over/step_into/step_out and pause_execution blocked on the stopped DAP event with a hardcoded 5s deadline and returned a spurious success:false ("Step operation did not complete within 5s") when the debuggee was simply still executing — e.g. stepping over a long-running call, or pausing a target blocked in native code. The eventual stop was then absorbed silently by the background listener, so the user was told the step failed and the session later flipped to paused with no report. This was previously observed in real use (docs/archive/MCP_DEBUGGER_COMPREHENSIVE_TEST_RESULTS.md).

These timeouts imposed a fixed requirement on debuggee runtime behavior; they existed to keep the wait finite, not because a slow step is an error.

Fix — truthful async semantics

Mirror how continue_execution already works (ack now, stop reported asynchronously):

  • Keep a short grace wait (fast steps still return the new location synchronously). On expiry, return success:true, state:"running", pending:true with a message explaining the session becomes paused when the operation completes — the persistent handleStopped listener (which has no timeout) already handles that transition.
  • Extract the 5000 literals into protected stepGraceMs/pauseGraceMs fields (test-shrinkable), matching the existing attach* field pattern.
  • Replace ErrorMessages.stepTimeout/pauseTimeout with informational stepStillRunning/pausePending factories.
  • Surface pending:true and the truthful message in the step tool response instead of an unconditional "Stepped over".
  • Document the blocking contract in the step_*/continue_execution/pause_execution tool descriptions so LLM clients know a pending step is not a failure.
  • Update the pattern docs (docs/patterns/error-handling.md, event-management.md) to show the grace-window pattern.

Testing

  • Updated the tests that pinned the old failure behavior; added a test pinning the async completion path (late stoppedPAUSED).
  • Full unit suite (2354) + integration pass; lint clean.
  • Verified live end-to-end: stepping over a time.sleep(15) in a Python session returns the pending success after ~5s (previously a spurious failure), the session flips to paused at the post-sleep line when the sleep ends, and a trivial step still returns immediately with location/context.

Follow-ups filed from the same audit

🤖 Generated with Claude Code

…es grace window

Step over/into/out and pause_execution blocked on the 'stopped' DAP event
with a hardcoded 5s deadline and returned a spurious success:false
("did not complete within 5s") when the debuggee was simply still
executing — e.g. stepping over a long-running call. The eventual stop was
then absorbed silently by the background listener.

These timeouts imposed a fixed requirement on debuggee runtime behavior.
Convert them to grace windows with truthful async semantics, mirroring
continue_execution:

- On expiry, return success:true with state 'running' and data.pending,
  plus a message explaining the session becomes 'paused' when the
  operation completes (the persistent handleStopped listener already
  handles that transition, with no timeout).
- Extract the 5000ms literals into protected stepGraceMs/pauseGraceMs
  fields (test-shrinkable), matching the attach* field pattern.
- Replace ErrorMessages.stepTimeout/pauseTimeout with informational
  stepStillRunning/pausePending factories.
- Surface pending:true and the truthful message in the step tool
  response instead of an unconditional "Stepped over".
- Document the blocking contract in the step/continue/pause tool
  descriptions so LLM clients know a pending step is not a failure.
- Add a test pinning the async completion path (late stop -> PAUSED).

The remaining debuggee-dependent timeouts found in the same audit are
tracked separately: evaluate_expression request timeouts (#142) and the
attach thread-verification window (#143).

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

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

debugmcpdev and others added 2 commits July 7, 2026 13:12
Codecov flagged the new pending branch in the step_over/into/out handler
as uncovered. This is the layer an MCP client actually sees, so pin the
contract: a pending DebugResult from the session manager must surface as
success:true with state 'running', pending:true, the truthful
still-running message (not "Stepped X"), and no location.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@debugmcpdev debugmcpdev merged commit 4e0bf45 into main Jul 7, 2026
10 checks passed
@debugmcpdev debugmcpdev deleted the fix/step-pause-grace-window branch July 7, 2026 10:40
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.

2 participants