Skip to content

fix(#529): latch child 'initialized' events so a same-chunk arrival can't stall adoption - #531

Merged
debugmcpdev merged 1 commit into
mainfrom
fix/529-latch-post-attach-initialized
Aug 27, 2026
Merged

fix(#529): latch child 'initialized' events so a same-chunk arrival can't stall adoption#531
debugmcpdev merged 1 commit into
mainfrom
fix/529-latch-post-attach-initialized

Conversation

@debugmcpdev

Copy link
Copy Markdown
Collaborator

Fixes #529.

What happened

The e2e test mcp-server-smoke-js-function-bp.test.ts > "honors conditions evaluated in the callee scope" failed in a full-suite run: no pause was ever observed, because the pre-launch function breakpoint on compute never armed and the fixture ran to completion.

The per-session logs of that exact run tell the story (full timeline in #529): the child's post-attach initialized event arrived in the same socket chunk as the attach response (18.056 vs 18.055). MinimalDapClient dispatches events synchronously while the response's awaiter is still parked in the microtask queue, so the waitForEvent('initialized', 3000) listener that handlePostAttachInit registers after the response resolves never saw it. Adoption stalled for the full 3 s, cdpBridge.attachToChild ran only at 21.058, and by then the forced entry pause — the one pause where an ESM module-scoped name like compute can be resolved via Debugger.evaluateOnCallFrame — had been held 300 ms for a CDP pause record that could never arrive, forwarded, and auto-continued. Nothing armed; zero stops; silent run-through.

The bridge's own guards (processStoppedEvent awaits attachInFlight and waitForResolution()) were bypassed because the attach hadn't even started when the entry stop passed through.

The fix

Latch child initialized events: wireChildEvents counts them from connect time, and both waiters (initializeChild's 12 s wait and handlePostAttachInit's 3 s wait) consume the latch against a baseline snapshotted before their triggering request is sent. Same shape as the #515 init-ACK latch. The latch check and waitForEvent's listener registration share one synchronous frame, so no event can slip between them.

With the stall gone, the bridge attaches ~150 ms after the child attach (as in passing runs), attachInFlight is set before the entry stop is processed, and the existing sticky Debugger.paused replay + resolution-await machinery makes the entry-pause bind deterministic.

Tests

  • New mock knob emitInitializedSyncOn reproduces same-chunk delivery (event emitted synchronously inside sendRequest, before the caller's await resumes).
  • does not stall adoption when the post-attach initialized rides the attach response (issue #529) — fails without the fix (adoption still pending at t+1 s), passes with it; also asserts the post-attach mirror re-send ran, proving the latch (not the timeout) resolved the wait.
  • does not stall adoption when initialized rides the initialize response (issue #529) — same race at the initialize request (12 s wait).
  • Verified both new tests fail on the unfixed source.
  • tests/e2e/mcp-server-smoke-js-function-bp.test.ts — full file (6 tests) run 3× against the rebuilt server: green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RiTRwc8dshMYgmbp5o5m5t

…an't stall adoption

The post-attach 'initialized' event can share a socket chunk with the
attach response. MinimalDapClient dispatches events synchronously while
the response's awaiter is still parked in the microtask queue, so the
waitForEvent listener that handlePostAttachInit registers afterwards
never sees it — the 3s wait times out in full, and the CDP
function-breakpoint bridge attaches only after the forced entry pause
has been auto-continued. Pre-launch function breakpoints on ESM
module-scoped names then silently never arm (they can only resolve at a
pause, and the missed entry pause was the last one).

Count 'initialized' events from wireChildEvents onward and have both
waiters (initializeChild's and handlePostAttachInit's) consume the
latch against a baseline snapshotted before their triggering request is
sent — same shape as the #515 init-ACK latch. The latch check and
waitForEvent's listener registration share one synchronous frame, so no
event can slip between them.

Fixes #529

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RiTRwc8dshMYgmbp5o5m5t
@codecov

codecov Bot commented Aug 27, 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 36d415c into main Aug 27, 2026
10 checks passed
@debugmcpdev
debugmcpdev deleted the fix/529-latch-post-attach-initialized branch August 27, 2026 19: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

1 participant