Skip to content

feat: onResume option on connect/resume - #1800

Draft
bchalios wants to merge 4 commits into
mainfrom
feat/on-resume-option
Draft

feat: onResume option on connect/resume#1800
bchalios wants to merge 4 commits into
mainfrom
feat/on-resume-option

Conversation

@bchalios

@bchalios bchalios commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Resuming a paused sandbox always restores its memory snapshot, so a snapshot whose memory image wedges the guest has no self-serve way out. onResume: 'reboot' cold-boots from the sandbox's disk state instead and leaves the memory snapshot untouched, so a later 'restore' still works. 'restore' remains the default and keeps travelling as an omitted memory field — only the 'reboot' literal sends memory: false, so an unrecognized value from an untyped caller falls back to a memory restore rather than silently cold-booting.

// Recover a paused sandbox whose restored memory hangs on wake.
const sandbox = await Sandbox.connect(sandboxId, { onResume: 'reboot' })

// Default: restore the memory snapshot, processes and connections intact.
const sameSandbox = await Sandbox.connect(sandboxId)
sandbox = Sandbox.connect(sandbox_id, on_resume="reboot")
sandbox = await AsyncSandbox.connect(sandbox_id, on_resume="reboot")

Disk state carries crash-recovery semantics, so writes not flushed before the pause may be lost. Where filesystem-only resume is not enabled, a 'reboot' that would actually drop memory is rejected with an error rather than quietly restoring it. The no-op cases (a snapshot holding no memory, a sandbox still running) never reach that gate and succeed either way — the API short-circuits them before it consults the flag.

Draft until the spec pin is real. These fields are still marked x-not-implemented: true upstream, and redocly.yaml's filter-out prunes the marked property, so codegen at any current pin produces clients with no memory field at all. Pending the infra commit that drops the marker, spec/openapi.yml here is hand-placed at its expected post-sync content and spec/infra-ref is a placeholder — the Generated files check is expected red. Before this leaves draft the pin gets repointed at the real commit and make codegen re-run, so the tracked spec comes from Copybara and is byte-identical to upstream.

The rest of the spec diff is churn the pin had fallen behind by: removed access-token models, httpsPorts and reworded transform rules on the network config, Rig* schemas that survive the tag filter as orphan components, and the resume request body becoming optional.

Verified: 5 JS + 10 Python request-shape tests over sync, async, instance and static-by-id forms, each assertion mutation-checked (flip the literal to !== 'restore', force false, drop the guard, drop each forwarding hop — every mutant reddens exactly the intended row). pnpm run lint, format and typecheck clean. The credential-backed suites can't run without an E2B_API_KEY: locally they fail identically to main (231 JS, 426 Python, byte-identical failure sets), so CI's run is the real check there.

Picks up `memory` on ConnectSandbox and ResumedSandbox, which the API
serves and the spec no longer marks unimplemented. The rest is churn the
pin had fallen behind: the removed access-token models, httpsPorts and
the reworded transform rules on the network config, and Rig schemas that
survive the tag filter as orphan components.

Signed-off-by: Babis Chalios <babis.chalios@e2b.dev>
Resuming a memory-inclusive snapshot whose memory image wedges the guest
had no self-serve way out. `onResume: 'reboot'` (`on_resume` in Python)
cold-boots from disk state instead, and the memory snapshot is never
touched, so a later restore still works.

'restore' is the API's own default and travels as an absent `memory`
field; only the 'reboot' literal sends `memory: false`, so an unrecognized
value from an untyped caller falls back to a memory restore rather than
silently cold-booting.

Signed-off-by: Babis Chalios <babis.chalios@e2b.dev>
@cla-bot cla-bot Bot added the cla-signed label Sep 3, 2026
@cursor

cursor Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

PR Summary

Medium Risk
Changes paused-sandbox resume semantics when callers opt into reboot (possible data loss from crash-recovery disk semantics); default connect behavior is unchanged.

Overview
Adds onResume / on_resume to Sandbox.connect() in the JS and Python SDKs so callers can choose how a paused sandbox wakes up. 'restore' (default) keeps the existing behavior and does not send a memory field on the connect API. 'reboot' sends memory: false to cold-boot from disk only, leaving the memory snapshot intact for a later restore—intended when restoring memory wedges the guest.

The option is keyword-only in Python, works on static and instance connect, and only the exact 'reboot' literal opts into cold boot; other values omit memory so untyped callers do not accidentally reboot. New request-shape tests cover JS and sync/async Python.

OpenAPI / generated client churn from the updated spec pin: optional memory on ConnectSandbox / ResumedSandbox, resume body optional, removed access-token schemas and routes, httpsPorts and expanded egress HTTPS transform docs, Rig* admin schemas, AdminJWTAuth, and a 501 response. Infra ref and hand-placed spec are noted as draft until upstream drops x-not-implemented and codegen is re-run.

Reviewed by Cursor Bugbot for commit 39424d6. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot

changeset-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 39424d6

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

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

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

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Package Artifacts

Built from aa6714c. Download artifacts from this workflow run.

JS SDK (e2b@2.46.2-feat-on-resume-option.0):

npm install ./e2b-2.46.2-feat-on-resume-option.0.tgz

CLI (@e2b/cli@2.18.1-feat-on-resume-option.0):

npm install ./e2b-cli-2.18.1-feat-on-resume-option.0.tgz

Code Interpreter JS SDK (@e2b/code-interpreter@2.7.3-feat-on-resume-option.0):

npm install ./e2b-code-interpreter-2.7.3-feat-on-resume-option.0.tgz

Desktop JS SDK (@e2b/desktop@2.3.5-feat-on-resume-option.0):

npm install ./e2b-desktop-2.3.5-feat-on-resume-option.0.tgz

Python SDK (e2b==2.46.4+feat.on.resume.option):

pip install ./e2b-2.46.4+feat.on.resume.option-py3-none-any.whl

Code Interpreter Python SDK (e2b-code-interpreter==2.9.3+feat.on.resume.option):

pip install ./e2b_code_interpreter-2.9.3+feat.on.resume.option-py3-none-any.whl

Desktop Python SDK (e2b-desktop==2.4.6+feat.on.resume.option):

pip install ./e2b_desktop-2.4.6+feat.on.resume.option-py3-none-any.whl

@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 SDK-facing changes (packages/js-sdk/src/sandbox/sandboxApi.ts, index.ts, packages/python-sdk/e2b/sandbox/sandbox_api.py, sandbox_sync/, sandbox_async/, __init__.py, and the new tests) against: parity (T-1, T-2, T-10), API shape (T-3, T-6, T-14, T-15, T-19, T-22, T-23), boundary mapping (T-18), entry-point exports (T-54), client-side validation (T-52), and docs (T-69–T-74). Generated files (schema.gen.ts, api/client/models/*, spec/) were not judged.

Violations: 1 (low severity, same issue mirrored in sync/async Python — flagged inline on both).

  • T-3 / T-1don_resume lands as another positional-with-default in the Python connect() signatures, extending the existing timeout into a chain of optional positionals (Sandbox.connect(id, 300, "reboot") now type-checks). Since JS carries it in SandboxConnectOpts, the Python mirror should be keyword-only. timeout/logger are pre-existing and out of scope, so a * separator can't be dropped in without moving them; noted as a design-debt item rather than something to block on.

Everything else lines up well: 'restore' | 'reboot' instead of a memory: boolean mirror of the wire field (T-14); the wire boolean is mapped at the boundary and the generated ConnectSandbox type never reaches users (T-18); SandboxOnResume is a named, exported type (export type in JS, __all__ in Python — T-23, T-54); no client-side check of whether the deployment supports filesystem-only resume (T-52); all four Python connect docstrings plus the JS option JSDoc document the parameter, default, and rejection behavior (T-69, T-74). Observation, not a violation: onResume/on_resume reuses the on<Event> shape T-27 reserves for callbacks, but onTimeout already set that precedent for lifecycle action config, so consistency wins here.

Comment thread packages/python-sdk/e2b/sandbox_sync/main.py Outdated
Comment thread packages/python-sdk/e2b/sandbox_async/main.py Outdated
Landing it after `timeout` made `Sandbox.connect(id, 300, "reboot")` a
valid call, while JS only accepts the option through SandboxConnectOpts.
Placing it after `logger` behind a `*` separator keeps both pre-existing
parameters positional, so nothing existing moves.

Signed-off-by: Babis Chalios <babis.chalios@e2b.dev>
The gate short-circuits a memoryless snapshot before it consults the
feature flag, and a running sandbox never reaches the gate, so "not
enabled rejects reboot" was only true for a paused snapshot that holds
memory.

Signed-off-by: Babis Chalios <babis.chalios@e2b.dev>
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.

1 participant