Skip to content

fix(proxy): thread per-request DAP timeout through parent/worker/socket for evaluate and redefine (fixes #142)#148

Merged
debugmcpdev merged 1 commit into
mainfrom
fix/dap-request-timeout-override
Jul 7, 2026
Merged

fix(proxy): thread per-request DAP timeout through parent/worker/socket for evaluate and redefine (fixes #142)#148
debugmcpdev merged 1 commit into
mainfrom
fix/dap-request-timeout-override

Conversation

@debugmcpdev

Copy link
Copy Markdown
Collaborator

Fixes #142.

Problem

evaluate_expression (and redefine_classes) rode three stacked hard timeouts — parent 35s, worker request-tracker 30s, socket 30s — so a long-running expression spuriously failed even though nothing was wrong. Unlike the step/pause case (#144) there is no async completion path: the DAP response is the result, so this needs a real threaded timeout override, not a "pending" marker.

Change

Thread a per-request timeoutMs end to end, preserving layer ordering (worker/socket fire first, parent last):

tool arg `timeout` (ms, evaluate_expression + redefine_classes)
  → SessionManagerOperations.evaluateExpression / redefineClasses   [validate >0 finite, clamp 600000]
  → proxyManager.sendDapRequest(cmd, args, { timeoutMs })           [parent timer = (timeoutMs ?? 30000) + 5000 margin]
  → IPC DapCommandPayload.timeoutMs                                 [parser drops non-finite/non-positive values]
  → worker: requestTracker.track(..., timeoutMs) + dapClient.sendRequest(..., timeoutMs)
  → MinimalDapClient (already supported per-request timeouts; now actually receives them)
  • Defaults unchanged when timeout is omitted (30s worker/socket, 35s parent) — the parent timer is now derived instead of the hardcoded 35000, and its error reports the actual deadline.
  • Worker timeout failures now report the effective deadline: Request 'evaluate' timed out after 30s.
  • Timeout failures on these two tools append a hint naming the timeout argument (ErrorMessages.dapRequestTimeoutHint).
  • Fixed a pre-existing interface/impl mismatch: IRequestTracker.track's timeoutMs is now optional, matching both implementations.

Verification

  • Unit: 149 files / 2379 tests green (includes new tests at every layer: tracker callback receives effective timeout; parent honors override + margin and keeps timeoutMs out of the IPC payload when unset; worker forwards to tracker + socket incl. queued js-debug commands; parser normalization; session-manager validation/clamp/hint and stackTrace pre-request keeping the default; server schema + forwarding for both tools).
  • Integration: green. Lint + tsc clean.
  • E2E (Python via dev-proxy): evaluate_expression of a 45s-sleeping expression with timeout: 120000 → succeeds with the correct result where the old stack failed at 30s; the same expression with defaults still fails truthfully at 30s, now with the hint: Request 'evaluate' timed out after 30s. If the operation is expected to take this long, retry with a larger 'timeout' (ms) argument. Note the operation may still be running in the debuggee.

Out of scope (noting on #142)

  • Launch-request slowness: the js-debug launch barrier path is fire-and-forget and other adapters' launch keeps the default.
  • DAP has no cancel support — an expired evaluate keeps running in the debuggee after the timeout fires.

Note for merging

PR #147 (#143, attach verifyTimeout) is still open and also touches src/utils/error-messages.ts, src/server.ts, and src/session/session-manager-operations.ts in different regions — whichever merges second may need a trivial conflict resolution (both append new ErrorMessages factories).

🤖 Generated with Claude Code

…et for evaluate and redefine (fixes #142)

evaluate_expression and redefine_classes previously rode three stacked hard
timeouts (parent 35s, worker request-tracker 30s, socket 30s), so a
long-running expression spuriously failed even though nothing was wrong.
Unlike step/pause there is no async completion path - the DAP response IS
the result - so this threads a real timeout override end to end:

- tool arg `timeout` (ms) on evaluate_expression + redefine_classes
- SessionManagerOperations validates (positive, finite) and clamps to 600000ms
- ProxyManager.sendDapRequest gains options.timeoutMs; the parent timer is
  now derived ((override ?? 30000) + 5000 margin) instead of hardcoded 35000,
  preserving the invariant that the worker/socket timeout fires first
- DapCommandPayload.timeoutMs carries the override over IPC; the message
  parser drops non-finite/non-positive values defensively
- the worker forwards it to the request tracker and the DAP socket (which
  already supported per-request timeouts), and timeout failures now report
  the actual deadline ("timed out after Ns")
- timeout failures on these two tools append a hint naming the `timeout` arg

Defaults are unchanged when the argument is omitted (30s worker/socket,
35s parent). Launch-request slowness is explicitly out of scope, and DAP
has no cancel - an expired evaluate keeps executing in the debuggee.

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 debugmcpdev merged commit 0efdd29 into main Jul 7, 2026
10 checks passed
@debugmcpdev debugmcpdev deleted the fix/dap-request-timeout-override branch July 7, 2026 14:38
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.

evaluate_expression can spuriously time out on long-running expressions

2 participants