fix(#492): don't park ruby launches on an initialize response rdbg never sends - #507
Merged
Merged
Conversation
…ver sends rdbg can process the initialize request — provably, since it emits the 'initialized' event 34ms later — yet never write the response frame (its DAP send silently skips writing when the socket slot is momentarily unset). The worker awaited that response unconditionally, so the launch sat silent until the parent's 30s deadline killed the session with an error blaming adapter startup, even though an immediate retry always worked (3 occurrences across the last 48-combo sweep). Launch initialization for policies declaring the new initializeResponseOptional behavior (ruby only) now races the response against the already-armed 'initialized' event plus a 2s grace period. When the event wins, the launch proceeds with unknown capabilities — a documented-legal value every consumer already guards — a warning names exactly what happened, and a late response still captures capabilities (its eventual timeout rejection is swallowed). Attach sessions and other adapters keep the strict await. Verified end to end against a response-dropping fake rdbg: start_debugging now completes in ~4s (2s of it the grace period) instead of failing at 30s. Fixes #492 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This was referenced Aug 27, 2026
debugmcpdev
added a commit
that referenced
this pull request
Aug 27, 2026
…#510) Self-debugging RCA writeup of the #492 Ruby launch stall: the incident log, why #470's decoder paths were ruled out, mcp-debugger stepping through its own compiled MinimalDapClient (statement breakpoints in dist/, evaluate_expression against pendingRequests, the stack of the event-delivery moment), the fake-rdbg replay with its DAP trace, the rdbg 1.11.0 source analysis (silent `if sock = @sock` drop guard, unsynchronized cleanup_reader) with the seq discriminator for the next live capture, and what #507/#509 changed. Honest about proven vs. hypothesized. Also: troubleshooting gains a section decoding the stage-aware 30s init-timeout message (post-#493), the ruby guide's troubleshooting table maps the new recovery warning to the case study, and the README documentation index links it. Co-authored-by: JF <john.franklin@gmail.com> Co-authored-by: Claude Fable 5 <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.
Summary
rdbg can process the
initializerequest — provably, since it emits theinitializedevent 34ms later — yet never write the response frame. The worker awaited that response unconditionally (dap-proxy-worker.tsinitialize step), so the launch sat silent until the parent's 30s deadline killed the session blaming adapter startup, even though an immediate retry always worked (3 occurrences across the last 48-combo sweep; full evidence in #492).The fix
InitializationBehavior.initializeResponseOptionalflag, declared only byRubyAdapterPolicy— launch mode only, attach keeps the strict await (ruby attach shares the policy, so the worker guard also excludes attach explicitly).awaitInitializeResponse()races the response against the already-armedinitializedevent plus a 2s grace period (matching the existing Phase-1 wait convention). Event wins ⇒ warn naming exactly what happened (greppable, references Ruby/rdbg: 30s proxy-init timeout recurs after #470 — initialize response never arrives, with no framing error this time #492) and proceed withcapabilities = undefined— a documented-legal value every consumer already guards. A late response still captures capabilities; its eventual timeout rejection is swallowed so it can't become an unhandled rejection. The grace timer is cleared when the response wins — no stray timers.istanbul ignoreon the launch-before-config branch — the new tests exercise it.Why proceeding is safe
The DAP spec makes
initializedstrictly follow successfulinitializeprocessing, so the event is proof the request was served; a dropped response will never arrive (DAP has no resend). Nothing downstream hard-requires capabilities: the worker's config sequence derives from policy, and every parent consumer guards absence (supportsExceptionInfoRequest?., logpoint downgrade check, mirror?? undefined).Residual: an rdbg wedged for all responses (launch too) still hits the 30s deadline — that case is what #493's diagnostics (next PR) will name honestly.
Tests
initializeSession+ firedinitialized⇒ launch/configurationDone proceed, warn logged, no capabilities status (timed out before the fix).start_debuggingcompletes in ~4s (2s = grace) with the recovery warn in the proxy log, instead of failing at 30s with the misleading message.Fixes #492
🤖 Generated with Claude Code