Repository navigation
session tree M1 (2/3): tree-aware wait_for_stop with sessionId and children, and the cascade answers (#1081) - #1090
Merged
Merged
Conversation
…#1081 step 1) selectSettledInTree is the four-rule read the tree-aware wait_for_stop answers by: the session itself when paused or in error, else the first paused or errored descendant in adoption order depth first, else the session itself when ended, else keep waiting. session-state-wait.ts now has one waitForSettle primitive (the #849 microtask re-read, abort, timeout, gone) behind an unchanged waitForSessionState and a new waitForSubtreeSettle, fed by the core's subtree listeners: a session's transition or removal tells its own subtree listeners and each ancestor's, so a root's waiter learns of a child's adoption (its first transition), its stops and its removal. The context gains onSubtreeChange and descendantsOf (pre-order). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ession, children lists the descendants (#1081 step 2) waitForStop observes the session and every descendant, present or adopted while it waits, through waitForSubtreeSettle, and answers for the session the four-rule selection names: the ordinary stop answer (landing, snapshot, lastStop, exitCode, programEnd) read on that session, with `sessionId` on every branch — pending included — and `children` (every descendant, depth first in adoption order, each with its state and parent) when the session has any. An ended descendant alone never completes an ancestor's wait; a descendant closed meanwhile is one fewer, not the end of the wait. The TreeAnswerFields type (children, childrenReleased, childrenClosed) joins the launch answer's type for the next step. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ldren, and the handlers' tree fields (#1081 step 3) A child released to run undebugged (beyond the cap, no adopter, a failed adoption — never one taken while its subtree was closing) counts on the parent as releasedChildReports, reset per launch and carried by the wait and launch answers as childrenReleased: a running total, never consumed. stopProxyPreservingSession returns how many descendants the teardown closed, and a relaunch answers childrenClosed with it; the launch and hot-restart answers list the children adopted during the hold. The wait handler copies sessionId and reads the landing (line context, stale build) for the selected session; start, restart and detach hoist children, childrenClosed and childrenReleased beside pending. The detach answer is typed for its fields. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…thon diagnostic no longer waits for it (#1081 step 4) One sentence on the tool: a session whose program spawned child sessions is waited on as a tree, sessionId names the session the answer is about and children lists the descendants. The 24 tool-list snapshots follow. The Python launch diagnostic for an explicit subProcess: true says the parent's wait_for_stop reports the child's stop, and that the default flips in the last step of #1081. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…eased, childrenClosed, childrenReleased (#1081 steps 5–6) closeSessionReporting owns the cascade and says what the close did: the rows removed (descendants first, deepest first — the handler forgets each output resource), childrenClosed, processEnded for a child whose process the close ended (the policy's closeChild pin; debugpy terminates), released for an attach root, whose descendants are detached with their targets kept running like its own. closeSession wraps it. A child view's stop takes { terminateDebuggee: false } for that release. detach_from_process on a parent releases every descendant first, deepest first, before the root's disconnect, keeps their rows as stopped with a program end that says released, and answers childrenReleased; a terminating detach closes the subtree and answers childrenClosed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…he guides, the skill and the fragment (#1081 step 7) wait_for_stop's reference gains the four-rule selection, sessionId, children, childrenReleased and a worked answer; start_debugging, restart_debugging, detach_from_process and close_debug_session name their fields; the parentSessionId paragraph, the Python guide, KNOWN_ISSUES, the skill and the server instructions say the parent's wait reports a child's stop; the design doc's status names the merged PR 1 and this PR; CLAUDE.md names the pieces. A changelog fragment for this PR; PR 1's no longer waits for the tree-aware wait. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rts the child's stops, restart and close answer their counts (#1081 step 8) A breakpoint in the child's code only, the parent started, a wait on the parent: three times the child's stop with the child's sessionId and the children list, then the parent's own end with exit 0 once the child is done. A restart closes the previous run's child (childrenClosed: 1) and pauses the new run; closing a child answers processEnded, closing the parent childrenClosed: 1. The tree's first wait names the parent when the parent is the one paused. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…he parent's own end (#1081) The wait_for_stop description and the fragment said "the session itself when it is paused or ended, else the first paused descendant", which inverts rules 2 and 3 for the one case the design singles out — a parent that ended while a child is paused reports the child. Now: the session itself when paused, else the first paused descendant, else the session itself when ended. The 24 snapshots follow. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This was referenced Nov 5, 2026
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.
PR 2 of 3 for #1081 (the session tree, design; PR 1 was #1087). The designed flow — set a breakpoint in the child's code, start the parent, wait — and the answer fields the cascades owe. Still behind an explicit
dapLaunchArgs: { subProcess: true }; the default flips in PR 3.What changes for a caller
wait_for_stopis tree-aware. A wait on a session observes it and every descendant, present or adopted while it waits, and answers for the session a fixed priority names: the session itself if paused or in error; else the first paused or errored descendant, depth first in adoption order; else the session itself if ended; else it keeps waiting — an ended child alone never completes the parent's wait. Level-triggered as before (nothing consumed, the auto-continued entry stop still never reported). Every answer carriessessionId(the selected session; the one asked for on a plain session or a pending answer) and the ordinary stop answer for that session (lastStop,location,context,snapshot,exitCode,programEnd); a session with descendants also carrieschildren: [{ sessionId, name, state, parentSessionId }], the pending answer included. A session with no children answers byte-for-byte as before plussessionId.start_debugging/restart_debuggingcarrychildren(adopted during the hold),childrenReleased(children that run undebugged this run: beyondDEBUG_MCP_MAX_CHILD_SESSIONS, or an adoption that failed — a per-run running total) andchildrenClosed(a relaunch closes the previous run's children first). A hot restart keeps its children.close_debug_sessionanswerschildrenClosedwhen it took descendants with it,processEnded: truefor a child whose process it ended (debugpy),released: truefor an attach root (its descendants detached with their targets kept running, like its own). Every closed descendant's output resource is forgotten too (it was leaked before).detach_from_processon an attach root releases the children first, deepest first (disconnectwithterminateDebuggee: false), then the parent; the child rows stay,stopped, withprogramEnd.data.released: true; the answer sayschildrenReleased.terminateProcess: truecloses the subtree (childrenClosed).wait_for_stoptool description gains one sentence on the tree (24 tool-list snapshots follow); the PythonsubProcess: truediagnostic says the parent's wait reports the child.How
src/session/execution/tree-settle.ts—selectSettledInTree, the pure four-rule read;describeTreeAnswerbuildschildren/childrenReleased.src/session/execution/session-state-wait.ts— onewaitForSettleprimitive (the wait_for_stop: block until a session's next stop or end instead of polling list_debug_sessions #849 microtask re-read, abort, timeout, gone) behind an unchangedwaitForSessionStateand the newwaitForSubtreeSettle.SessionManagerCore— subtree listeners (onSubtreeStateChange: a session's transition or removal tells its own listeners and each ancestor's; a child's first transition is its adoption),descendantsDepthFirst,releasedChildReports(bumped inadoptReportedChild's release, never for a release taken while the subtree is closing; reset per launch),closeSessionReporting(the cascade with its outcome;closeSessionwraps it),closeSingleSession's release mode,releaseDescendants(the detach cascade),stopProxyPreservingSessionreturns the count.ExecutionController.waitForStopcomposes the answer for the selected session; the handler copiessessionIdand reads the landing (line context, stale build) for it;hoistTreeFieldslifts the tree fields besidependingon start, restart and detach; the close handler answers from the outcome.ChildProxyView.stop({ terminateDebuggee })for the release mode;isChildProxyViewguard.Proven
tree-settle.test.ts(the rules),session-state-wait.test.ts(+ the subtree wait),session-manager-wait-for-stop-tree.test.ts(13 cases: rules 1–4, pre-order, adoption during the wait, a descendant closed during the wait, a wait on a child, pending withchildren, an errored child),session-manager-child-sessions.test.ts(+ the counters, the launch'schildren, the close outcomes, the detach cascade order), the handler tests, the view test.tests/adapters/python/integration/python-subprocess-tree.test.ts(6 cases): the designed flow — a breakpoint inchild.pyonly, the parent'swait_for_stopanswers the child's stop three times with the child'ssessionIdandchildren, then the parent's end with exit 0; a restart answerschildrenClosed: 1; closing a child answersprocessEnded: true; closing the parentchildrenClosed: 1.typecheck:allcheck-docs(145 files),changelog:checkThe first push was blocked by the two
bridge-tree-killcases, a Windows timing flake in untouched code (the bridges' 300 ms tree-kill grace; passes on main and standalone): filed as #1089; the second hook run was clean.Calls made here (overturnable)
childrenlists every descendant (pre-order), each withparentSessionId; the design's{ sessionId, name, state }is a subset.childrenReleasedis a per-run running total (nothing consumed, concurrent waiters agree); it counts every release that let a child run undebugged, never one taken while the subtree was closing.childrenClosedalso rides onclose_debug_sessionand on astart_debuggingthat tore down a live run.childrenClosed).wait_for_stopdescription changed; the other four tools' text is unchanged.Not in this PR
PR 3: the
subProcessdefault flip and the #1008 warning, the attach fixture (the attach-root close and the detach cascade ship here with unit tests and are measured there — no attach root has children until the attach default flips), the xdist and grandchild suites, theKNOWN_ISSUESentry, the skill rewrite.outputOnis js-debug's (M2, #1082). The idle policy is SDK step 2 (#1025).🤖 Generated with Claude Code