Skip to content

fix: death-aware, retryable js-debug child adoption; remove dead configDone deferral - #251

Merged
debugmcpdev merged 3 commits into
mainfrom
fix/js-child-session-robustness
Aug 4, 2026
Merged

fix: death-aware, retryable js-debug child adoption; remove dead configDone deferral#251
debugmcpdev merged 3 commits into
mainfrom
fix/js-child-session-robustness

Conversation

@debugmcpdev

Copy link
Copy Markdown
Collaborator

Fixes #248
Fixes #249
Fixes #250

First of two PRs for the cluster:js-child-session backlog chunk (#247 follows separately).

What changed

#250 — remove dead deferParentConfigDone machinery (commit 1). The 1500ms parent-configurationDone deferral could never arm on any real path: its only writer runs after createChildSession resolves, but the js-debug handshake sends parent configurationDone (step 4) before launch/attach (step 5), so no child can exist when the parent configDone goes out. Even force-armed, nothing ever resolved the deferral early — it always burned the full timer. Deleted along with the equally dead AdapterPolicy.shouldDeferParentConfigDone() and the DapClientBehavior.deferParentConfigDone flag from all eight policies (−233 lines).

#248 — death-aware adoption (commit 2). A child target dying mid-adoption used to burn every remaining step's full timeout (worst case ~400s in the attach retry loop; ensureChildStopped alone could add 80s) and left the half-adopted socket orphaned at worker shutdown:

  • New settle-once ChildDeathLatch (modeled on fix: js-debug launch barrier settles on debuggee death (fixes #242) #246's JsDebugLaunchBarrier) subscribes close/error right after connect; every adoption step is raced against it and aborts the moment the child dies.
  • attachChild gains a 60s total deadline on top of per-request timeouts, with death checks before/after each retry.
  • waitForEvent settles immediately on client death instead of waiting out its timer.
  • The failure path now rolls back childSessions/activeChild — previously hasActiveChildren() latched true forever, silently rejecting every subsequent adoption — and shuts down the failed child client.
  • MinimalDapClient.shutdown() now also invokes ChildSessionManager.shutdown() (previously unreachable from src), covering mid-adoption sockets never promoted via childCreated.

#249 — retryable adoption (commit 3). The policy-level adoptedTargets dedupe kept the pending-target id when adoption failed, so js-debug's re-sent startDebugging was answered {handled: true} without creating a child. The dispatch site (which already observes the rejection) now rolls the id back, and shutdown() clears the set.

Testing

  • 7 new unit tests: close-mid-attach aborts promptly, death at step boundary, attach total deadline, failure rollback + successful retry, manager shutdown reachability, re-sent startDebugging retries, adoptedTargets cleared on shutdown. The death tests run in ~35ms where the old behavior hung 15s+ (test-timeout kills).
  • Full gate: 2593 unit tests, 18 e2e smoke files, break-on-exceptions e2e 8/8, lint clean.

🤖 Generated with Claude Code

cynarlab and others added 3 commits August 3, 2026 22:21
…250)

The 1500ms parent-configurationDone deferral could never arm on any real
path: its only writer runs after createChildSession resolves, but the
js-debug handshake sends parent configurationDone before launch/attach,
so no child session can exist yet. Even when force-armed (tests only),
the deferral never settled early - nothing ever called its resolve; it
always burned the full 1500ms timer. Also removes the equally dead
AdapterPolicy.shouldDeferParentConfigDone() (no src consumer) and the
DapClientBehavior.deferParentConfigDone flag from all eight policies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A child target dying mid-adoption used to burn every remaining step's
full timeout window (worst case ~400s in the attach retry loop alone)
and left the half-adopted socket orphaned at worker shutdown.

- New settle-once ChildDeathLatch (modeled on JsDebugLaunchBarrier)
  subscribes close/error right after connect and aborts the in-flight
  adoption step the moment the child dies.
- attachChild gains a 60s total deadline on top of per-request timeouts
  and checks the latch before and after each retry.
- waitForEvent settles immediately on client close/error instead of
  waiting out its timer against a dead client.
- The failure path now rolls back childSessions/activeChild (previously
  hasActiveChildren() stayed true forever, silently blocking every
  subsequent adoption) and shuts down the failed child client.
- MinimalDapClient.shutdown() now also invokes ChildSessionManager
  shutdown, reaching mid-adoption sockets that were never promoted via
  childCreated; ensureChildStopped's pause calls get explicit 5s
  timeouts (were 30s defaults).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The js-debug reverse startDebugging handler adds the pending target id
to the client's adoptedTargets set before adoption runs, but nothing
removed it when createChildSession rejected - so a re-sent
startDebugging for the same target was answered {handled: true} without
ever creating a child, leaving the session silently undebugable.

The dispatch site (which already observes the rejection) now deletes
the id, resyncing the policy-visible set with ChildSessionManager's own
rollback; shutdown() additionally clears the set so a reused client
cannot carry stale adoption state.

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

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.50562% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/proxy/child-session-manager.ts 96.34% 3 Missing ⚠️
src/proxy/minimal-dap.ts 85.71% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@debugmcpdev
debugmcpdev merged commit fa29399 into main Aug 4, 2026
10 checks passed
@debugmcpdev
debugmcpdev deleted the fix/js-child-session-robustness branch August 4, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants