Skip to content

feat(sdk): support opt-in command group termination - #1801

Draft
tttboy123 wants to merge 3 commits into
e2b-dev:mainfrom
tttboy123:fix/command-kill-descendants-1034
Draft

feat(sdk): support opt-in command group termination#1801
tttboy123 wants to merge 3 commits into
e2b-dev:mainfrom
tttboy123:fix/command-kill-descendants-1034

Conversation

@tttboy123

@tttboy123 tttboy123 commented Sep 3, 2026

Copy link
Copy Markdown

Summary

Adds an explicit, backward-compatible way to terminate a command together with descendants that remain in its process group:

handle.kill(scope="group", request_timeout=30)
await handle.kill({ scope: 'group', requestTimeoutMs: 30_000 })

The existing default remains leader-only, so applications that intentionally keep subprocesses alive are not changed.

This SDK PR depends on e2b-dev/infra#3617, which adds the envd wire field and process-group implementation. It is ready for API review, but the infra dependency should land first.

Relates to #1034.

Reproduction and scope

Against hosted E2B with Python SDK 2.46.4, CommandHandle.kill() returned true in 0.322 seconds and removed the managed shell, but both sleep children remained alive and were restored after pause(keep_memory=True) / resume().

The reported indefinite block did not reproduce on the current SDK. This change therefore targets the confirmed lifecycle gap rather than changing timeout behavior. Pause/resume is also behaving as designed: a memory-preserving snapshot restores processes that were still alive when paused.

Design

  • Keep kill() PID-only by default for compatibility.
  • Add scope: "process" | "group" consistently to sync/async Python command and PTY kill methods, JS command/PTY kill options, and command handles.
  • Keep new Python scope parameters keyword-only and thread request timeouts through command handles, matching the existing JS handle options.
  • Send the new SendSignalRequest.descendants field only through generated protobuf types.
  • Gate explicit descendant termination on envd 0.7.1; old sandboxes fail clearly instead of silently ignoring an unknown protobuf field.
  • Reject invalid runtime scope values with the SDK's typed invalid-argument error before any RPC, rather than silently degrading a typo to leader-only termination.
  • Sync spec/infra-ref to the dependency commit and regenerate with make codegen.
  • Add patch changesets for the JS and Python SDK packages.

The process-group implementation covers the reproduced shell-child case. A descendant that deliberately creates a new session with setsid() can escape; a per-command cgroup plus cgroup.kill would be a separate, stronger lifecycle design.

Generated spec note

Moving spec/infra-ref from the previous pin to the dependency commit also picks up current infra API-spec drift (rig capacity models and network freeze metadata). Those generated REST client changes are not part of the command-kill behavior, but are included because the generated-files workflow runs make codegen, which fetches both the envd and REST specs from the same infra-ref. Removing only the REST changes leaves the worktree dirty in that required check. No generated wire types were hand-edited.

If maintainers prefer, I can move that deterministic pin/codegen delta into a prerequisite spec-sync PR; the feature PR would then need to target or rebase onto that prerequisite until it lands.

Validation

  • make codegen: pass (the private belt spec used the repository's documented tracked-copy fallback)
  • Python pytest -q tests/test_command_handle.py: 23 passed
  • Python offline top-level unit suite: 301 passed
  • Python ruff check, ruff format --check, and ty check: pass
  • JS command-handle suite: 19 passed
  • JS pnpm run build, pnpm run lint, and pnpm run typecheck: pass
  • Dependency envd: GOTOOLCHAIN=go1.26.6 go test -race ./...: pass
  • Dependency envd: golangci-lint v2.12.2 run --new-from-rev=upstream/main ./...: 0 issues

The full SDK suites also contain hosted integration tests. They were not used as evidence for this behavior because the new envd field is not deployed yet, and this environment did not inject a cloud API key into test commands. Offline unit coverage, generation, builds, lint, and type checks are clean.

Follow-up discussion

Please confirm whether the opt-in compatibility boundary is the desired public API. If E2B wants command handles to own their whole tree by default, or needs a guarantee across setsid(), that should be handled as a deliberate breaking/lifecycle decision rather than silently changing existing kill() behavior.

@cla-bot

cla-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have @tttboy123 on file. You can sign our CLA at https://e2b.dev/docs/cla . Once you've signed, post a comment here that says '@cla-bot check'

@changeset-bot

changeset-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5ae8eea

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
e2b Patch
@e2b/python-sdk Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TASTE.md review

Checked the changed SDK surface (Commands.kill, Pty.kill, CommandHandle.kill across JS / sync Python / async Python) against T-1 (cross-language parity), T-3 (positional vs. options), T-14 (enum over boolean), T-22/T-23 (named Opts types), T-46 (signal threading), T-54 (entry-point export), T-55 (ENVD_* version gating) and T-57/T-59 (error classes).

3 violations, all on the new descendants option:

  1. T-1 parity — JS CommandHandle.kill(opts?: CommandKillOpts) now accepts requestTimeoutMs/signal alongside descendants, but the Python CommandHandle.kill / AsyncCommandHandle.kill only take descendants (no request_timeout). The three surfaces should expose the same knobs.
  2. T-14 enum over booleandescendants: bool selects what gets killed (leader only vs. process group). A string-literal union/Literal such as scope: 'process' | 'group' reads better at the call site and leaves room for a future variant (e.g. a full process tree) without a breaking change. This applies to CommandKillOpts.descendants in JS and every Python descendants kwarg; I anchored only the JS interface and the sync Commands.kill signature to avoid repeating the same comment six times.
  3. T-3 optional positionals — in Python the new parameter is appended as a second optional positional after request_timeout, so sandbox.commands.kill(pid, 5.0, True) is now legal. Options should be keyword-only. Same pattern in sandbox_sync/commands/pty.py, sandbox_async/commands/command.py, sandbox_async/commands/pty.py.

Compliant: CommandKillOpts is a named, exported Opts type extending CommandRequestOpts (T-22/T-23/T-54); the envd gate uses a named ENVD_COMMANDS_DESCENDANTS constant and semver compare (T-55); SandboxError/SandboxException is the right class for a version-gate failure of a sandbox operation (T-57).

Not a TASTE point, but note the diff also carries unrelated regenerated spec/openapi.yml / packages/python-sdk/e2b/api/client/models/* churn (rigs, access-token removal) that isn't part of this feature — probably a stale spec/infra-ref bump worth splitting out.

Comment thread packages/js-sdk/src/sandbox/commands/index.ts Outdated
Comment thread packages/python-sdk/e2b/sandbox_sync/commands/command.py Outdated
Comment thread packages/python-sdk/e2b/sandbox_sync/commands/command_handle.py Outdated
Comment thread packages/python-sdk/e2b/sandbox_async/commands/command_handle.py Outdated
@cla-bot

cla-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have @tttboy123 on file. You can sign our CLA at https://e2b.dev/docs/cla . Once you've signed, post a comment here that says '@cla-bot check'

@tttboy123 tttboy123 changed the title feat(sdk): support opt-in command tree termination feat(sdk): support opt-in command group termination Sep 3, 2026
@tttboy123

Copy link
Copy Markdown
Author

Addressed all three TASTE review points in 46db6805a:

  • replaced the public boolean with scope: "process" | "group" / Literal["process", "group"];
  • made the new Python scope keyword-only;
  • added request_timeout to sync/async Python handles and threaded it to the command/PTY API for cross-language parity;
  • added PTY request coverage and re-ran both SDKs' tests and static checks.

On the generated REST churn: spec/infra-ref pins both envd and REST specs. The required generated-files workflow runs make codegen, which fetches both from that one commit. Restoring only spec/openapi.yml and its clients makes that check regenerate them and leave a dirty tree. I documented the drift in the PR body. If maintainers prefer a separate prerequisite spec-sync PR, I can split it that way and rebase this feature onto it; otherwise keeping the deterministic output with the pin is the repository's current codegen behavior.

@cla-bot

cla-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have @tttboy123 on file. You can sign our CLA at https://e2b.dev/docs/cla . Once you've signed, post a comment here that says '@cla-bot check'

@tttboy123

tttboy123 commented Sep 3, 2026

Copy link
Copy Markdown
Author

Addressed all three TASTE review points in 46db6805a, then added the runtime-validation hardening in 5ae8eea47:

  • replaced the public boolean with scope: "process" | "group" / Literal["process", "group"];
  • made the new Python scope keyword-only;
  • added request_timeout to sync/async Python handles and threaded it to the command/PTY API for cross-language parity;
  • added PTY request coverage and re-ran both SDKs' tests and static checks;
  • rejected invalid runtime scope values before the RPC with the SDK's typed invalid-argument error, preventing typo values from silently falling back to process-only behavior.

Fresh validation: Python command/PTY/handle tests 23/23, Python offline top-level unit tests 301/301, JS command/PTY/handle tests 19/19, and both SDKs' build/lint/type checks pass. make codegen also passes and leaves no additional generated drift.

On the generated REST churn: spec/infra-ref pins both envd and REST specs. The required generated-files workflow runs make codegen, which fetches both from that one commit. Restoring only spec/openapi.yml and its clients makes that check regenerate them and leave a dirty tree. I documented the drift in the PR body. If maintainers prefer a separate prerequisite spec-sync PR, I can split it that way and rebase this feature onto it; otherwise keeping the deterministic output with the pin is the repository's current codegen behavior.

@tttboy123
tttboy123 marked this pull request as ready for review September 3, 2026 20:54
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-03T20:58:25.446455Z 5ae8eea Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@tttboy123

Copy link
Copy Markdown
Author

@cla-bot check

@cla-bot cla-bot Bot added the cla-signed label Sep 4, 2026
@cla-bot

cla-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

The cla-bot has been summoned, and re-checked this pull request!

@mishushakov
mishushakov marked this pull request as draft September 4, 2026 15:13
@mishushakov

Copy link
Copy Markdown
Member

putting in draft until the backend PR merges

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants