-
Notifications
You must be signed in to change notification settings - Fork 3.1k
fix(dcode): improve managed first-run sessions #6680
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
1e7f3ae
fix(dcode): improve managed first-run sessions
jyaunches 0b3c56a
fix(dcode): document signal forwarding race
jyaunches 6554547
test(dcode): cover repeated session cleanup
jyaunches d162269
fix(dcode): refresh curl security pin
jyaunches cdf55f2
fix(dcode): close supervisor signal race
jyaunches 6b18844
test(dcode): exercise repeated cleanup check
jyaunches 89fde82
fix(dcode): bypass supervisor for identity
jyaunches 0edc466
fix(dcode): bypass supervisor for version
jyaunches 344df05
fix(dcode): preserve empty prompt validation
jyaunches 0c7dccc
test(dcode): preserve warning policy on re-onboard
jyaunches 1d3fca4
fix(dcode): supervise interactive sessions only
jyaunches 7fd5cfd
fix(dcode): install supervisor in runtime path
jyaunches 8c33f70
docs(dcode): clarify interactive supervision
jyaunches b0e8db1
test(dcode): wait for TUI composer readiness
jyaunches db5061f
Merge remote-tracking branch 'origin/main' into codex/fix-managed-dee…
jyaunches 1446e9e
test(e2e): align shared credential-free inventory
jyaunches 4512915
fix(dcode): harden supervisor platform boundary
jyaunches cb82e2b
fix(dcode): bound disconnect cleanup
jyaunches f67d366
merge(main): sync latest changes
jyaunches d05bdce
Merge branch 'main' into codex/fix-managed-deep-code-ux-6678
cv dc6e8b5
fix(dcode): observe late disconnect signals
jyaunches bc8c962
Merge remote-tracking branch 'origin/codex/fix-managed-deep-code-ux-6…
jyaunches File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
185 changes: 185 additions & 0 deletions
185
agents/langchain-deepagents-code/dcode-session-supervisor.py
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| """Reap processes started by one managed Deep Agents Code terminal session.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import ctypes | ||
| import errno | ||
| import os | ||
| import signal | ||
| import subprocess | ||
| import sys | ||
| import time | ||
| from collections.abc import Sequence | ||
| from pathlib import Path | ||
|
|
||
| _PR_SET_CHILD_SUBREAPER = 36 | ||
| _TERM_GRACE_SECONDS = 3.0 | ||
| _KILL_GRACE_SECONDS = 1.0 | ||
| _POLL_SECONDS = 0.05 | ||
|
|
||
|
|
||
| def _enable_child_subreaper() -> None: | ||
| """Adopt orphaned LangGraph descendants when the DCode process exits.""" | ||
| libc = ctypes.CDLL(None, use_errno=True) | ||
| if libc.prctl(_PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) != 0: | ||
| error = ctypes.get_errno() | ||
| raise OSError(error, os.strerror(error)) | ||
|
|
||
|
|
||
| def _direct_children() -> set[int]: | ||
| children: set[int] = set() | ||
| try: | ||
| entries = os.scandir("/proc") | ||
| except OSError: | ||
| return children | ||
| with entries: | ||
| for entry in entries: | ||
| if not entry.name.isdecimal(): | ||
| continue | ||
| try: | ||
| stat = Path(f"/proc/{entry.name}/stat").read_text(encoding="utf-8") | ||
| closing = stat.rfind(")") | ||
| fields = stat[closing + 2 :].split() | ||
| if closing != -1 and len(fields) >= 2 and int(fields[1]) == os.getpid(): | ||
| children.add(int(entry.name)) | ||
| except (FileNotFoundError, PermissionError, ValueError, OSError): | ||
| continue | ||
| return children | ||
|
|
||
|
|
||
| def _reap_exited_children() -> None: | ||
| while True: | ||
| try: | ||
| pid, _status = os.waitpid(-1, os.WNOHANG) | ||
| except ChildProcessError: | ||
| return | ||
| except InterruptedError: | ||
| continue | ||
| if pid == 0: | ||
| return | ||
|
|
||
|
|
||
| def _signal_children(children: set[int], sig: signal.Signals) -> None: | ||
| for pid in children: | ||
| try: | ||
| os.kill(pid, sig) | ||
| except ProcessLookupError: | ||
| continue | ||
| except PermissionError: | ||
| print( | ||
| f"dcode: cannot signal managed session descendant pid={pid}", | ||
| file=sys.stderr, | ||
| ) | ||
|
|
||
|
|
||
| def _cleanup_adopted_descendants() -> None: | ||
| """Terminate and reap every descendant associated with this launch.""" | ||
| deadline = time.monotonic() + _TERM_GRACE_SECONDS | ||
| signaled: set[int] = set() | ||
| while True: | ||
| _reap_exited_children() | ||
| children = _direct_children() | ||
| if not children: | ||
| return | ||
| new_children = children - signaled | ||
| if new_children: | ||
| _signal_children(new_children, signal.SIGTERM) | ||
| signaled.update(new_children) | ||
| if time.monotonic() >= deadline: | ||
| _signal_children(children, signal.SIGKILL) | ||
| break | ||
| time.sleep(_POLL_SECONDS) | ||
|
|
||
| kill_deadline = time.monotonic() + 1.0 | ||
| while time.monotonic() < kill_deadline: | ||
| _reap_exited_children() | ||
| children = _direct_children() | ||
| if not children: | ||
| return | ||
| _signal_children(children, signal.SIGKILL) | ||
| time.sleep(_POLL_SECONDS) | ||
| _reap_exited_children() | ||
|
|
||
|
|
||
| def _exit_code(returncode: int) -> int: | ||
| return returncode if returncode >= 0 else 128 + abs(returncode) | ||
|
|
||
|
|
||
| def _wait_after_disconnect(child: subprocess.Popen[bytes]) -> int: | ||
| """Bound shutdown even when the direct DCode child ignores disconnect.""" | ||
| try: | ||
| return child.wait(timeout=_TERM_GRACE_SECONDS) | ||
| except subprocess.TimeoutExpired: | ||
| child.terminate() | ||
| try: | ||
| return child.wait(timeout=_KILL_GRACE_SECONDS) | ||
| except subprocess.TimeoutExpired: | ||
| child.kill() | ||
| return child.wait() | ||
|
|
||
|
|
||
| def run(argv: Sequence[str]) -> int: | ||
| if not argv: | ||
| print("dcode session supervisor requires a command.", file=sys.stderr) | ||
| return 64 | ||
| if sys.platform != "linux": | ||
| print( | ||
| "dcode: session supervision requires a Linux OpenShell sandbox.", | ||
| file=sys.stderr, | ||
| ) | ||
| return 1 | ||
|
|
||
| _enable_child_subreaper() | ||
| child: subprocess.Popen[bytes] | None = None | ||
| pending_signals: list[int] = [] | ||
| disconnect_received = False | ||
|
|
||
| def forward(sig: int, _frame: object) -> None: | ||
| nonlocal disconnect_received | ||
| disconnect_received = True | ||
| if child is None: | ||
| pending_signals.append(sig) | ||
| return | ||
| try: | ||
| os.kill(child.pid, sig) | ||
| except (ProcessLookupError, PermissionError): | ||
|
github-code-quality[bot] marked this conversation as resolved.
Fixed
|
||
| # The child may exit between signal delivery and this forwarding | ||
| # attempt; cleanup below still reaps any adopted descendants. | ||
| pass | ||
|
|
||
| # Terminal-generated SIGINT already reaches every member of the foreground | ||
| # process group. Keep the supervisor alive to reap descendants without | ||
| # delivering a second Ctrl-C to DCode. OpenShell may target only the direct | ||
| # launcher for disconnect/termination signals, so those are forwarded. | ||
| signal.signal(signal.SIGINT, lambda _sig, _frame: None) | ||
| for sig in (signal.SIGHUP, signal.SIGTERM): | ||
| signal.signal(sig, forward) | ||
|
|
||
| try: | ||
| child = subprocess.Popen(list(argv)) | ||
| for pending_signal in pending_signals: | ||
| forward(pending_signal, None) | ||
| while True: | ||
| try: | ||
| returncode = child.wait(timeout=_POLL_SECONDS) | ||
| break | ||
| except subprocess.TimeoutExpired: | ||
| if disconnect_received: | ||
| returncode = _wait_after_disconnect(child) | ||
| break | ||
| finally: | ||
| _cleanup_adopted_descendants() | ||
| return _exit_code(returncode) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| try: | ||
| raise SystemExit(run(sys.argv[1:])) | ||
| except OSError as error: | ||
| if error.errno == errno.ENOSYS: | ||
| print("dcode: Linux child-subreaper support is unavailable.", file=sys.stderr) | ||
| else: | ||
| print(f"dcode: session supervisor failed: {error}", file=sys.stderr) | ||
| raise SystemExit(1) from error | ||
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
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.