feat(sdk): add sandbox fork to JS and Python SDKs - #1554
Conversation
Adds SDK support for the new POST /sandboxes/{sandboxID}/fork endpoint
(e2b-dev/runtime#3202): checkpoint a running sandbox in place and boot
`count` new sandboxes from that snapshot.
- spec: add SandboxForkRequest/SandboxForkResult schemas and the fork
path; regenerate JS and Python API clients via make codegen
- js-sdk: Sandbox.fork(sandboxId, opts) static and sandbox.fork(opts)
instance methods returning Promise<Array<Sandbox | SandboxError>>
(one entry per requested fork, allSettled-style)
- python-sdk: Sandbox.fork(sandbox_id, ...) / sandbox.fork(...) for both
sync and async SDKs returning List[Union[Sandbox, SandboxException]]
- tests: integration tests for single/multi fork, fork-by-id, killed
sandbox, plus offline unit tests for result mapping and count
validation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 572474f The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
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 |
PR SummaryMedium Risk Overview Reviewed by Cursor Bugbot for commit 572474f. Bugbot is set up for automated code reviews on this repo. Configure here. |
Package ArtifactsBuilt from ebb9229. Download artifacts from this workflow run. JS SDK ( npm install ./e2b-2.34.1-sandbox-fork-sdk.0.tgzCLI ( npm install ./e2b-cli-2.13.4-sandbox-fork-sdk.0.tgzPython SDK ( pip install ./e2b-2.33.0+sandbox.fork.sdk-py3-none-any.whl |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0bc6d730b8
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- map whole-request 404 to generic NotFoundError/NotFoundException (the 404 may be route-level, not necessarily a missing sandbox) and surface the API error message when present - don't fabricate a 500 code for per-fork failures missing an error body; only include a code when the API returned one - accept full connection options (proxy, logger, sandboxUrl, apiUrl, accessToken) in SandboxForkOpts, matching SandboxConnectOpts - preserve an explicit timeout=0 in the Python fork paths instead of silently replacing it with the 300s default - drop the mock-based JS unit test Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per-fork errors previously always produced a bare SandboxError / SandboxException. Extract the status-code-to-error-class mapping out of handleApiError / handle_api_exception into apiErrorFromCode / api_exception_from_code and reuse it for the error objects embedded in fork results, so e.g. a per-fork 429 (sandbox limit) surfaces as RateLimitError / RateLimitException. Fork return types widen to Array<Sandbox | Error> and List[Union[Sandbox, Exception]] accordingly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
There are 4 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f7ded9f. Configure here.
- detect the whole-request fork 404 from the response status instead of the parsed error body, which openapi-fetch leaves unset for empty bodies - map per-fork 404 error codes to NotFoundError/NotFoundException at the fork call sites, matching the whole-request 404 (the shared apiErrorFromCode/api_exception_from_code intentionally leaves 404 to callers — its meaning is call-site-specific, and a global branch would hijack 404s from callers passing custom classes like BuildError/VolumeError) - use plain test instead of the sandboxTest fixture for the killed-sandbox fork test, which creates its own sandbox Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
matthewlouisbrockman
left a comment
There was a problem hiding this comment.
probably main thing we need to fix next is the footgun where when snapshotting times out it kills the sandbox; is edge case but should address this sooner than later
A 404 from POST /sandboxes/{id}/fork means the source sandbox was not
found — the same semantics as pause/connect/setTimeout/getInfo — so
throw SandboxNotFoundError/SandboxNotFoundException there (message
still taken from the API body when present), consistent with the
sibling sandbox-by-ID operations. Per-fork 404s stay generic
NotFoundError/NotFoundException: they refer to a resource needed to
start that fork, not the source sandbox, which would have failed the
whole request.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## Summary Exposes `Sandbox.fork` (added to the SDKs in #1554) in the CLI as `e2b sandbox fork` (alias `sbx fk`). ``` e2b sandbox fork <sandboxID> [-n, --count <count>] [--timeout <seconds>] ``` - Output follows `sandbox create --detach`: one new sandbox ID per line on stdout, nothing else, so it composes in scripts. - `count`/`timeoutMs` are only forwarded when the flags are set, so the SDK/API defaults (1 fork, 300s) apply otherwise. `--timeout` reuses `parseTimeout` from `create.ts` (now exported) — same 30s minimum. - The SDK returns `Array<Sandbox | Error>` (`allSettled`-style). The command prints every successful ID, writes each per-fork error to stderr, and exits `1` if any fork failed. - `NotFoundError` on the source sandbox prints `Sandbox <id> wasn't found`, consistent with the other `sbx` commands. Linear: SDK-380 ### Usage ```sh $ e2b sbx create base --detach i2588449rqsrom0wxx5pu $ e2b sbx fork i2588449rqsrom0wxx5pu -n 2 --timeout 120 iksz0w8nufepy93i6x3k9 iek4cn32hmxwmqmnkgo8n $ for id in $(e2b sbx fork i2588449rqsrom0wxx5pu -n 3); do e2b sbx exec "$id" -- hostname; done ``` ### Testing - `packages/cli/tests/commands/sandbox/fork.test.ts` (mocked SDK): default call, count/timeout forwarding, partial failure exit code, not-found message, count validation. - Live against the API: created a sandbox, wrote `/tmp/marker`, forked with `-n 2 --timeout 120`; both forks contained the marker. Forking an invalid ID exits 1 with the API error. Link to Devin session: https://app.devin.ai/sessions/a139e98ad8a04d3dbdd082ed42752a5e Open in Devin Desktop: https://app.devin.ai/desktop/session/a139e98ad8a04d3dbdd082ed42752a5e?variant=devin Requested by: @mishushakov --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: mish@e2b.dev <mish@e2b.dev>

Summary
Adds SDK support for the new
POST /sandboxes/{sandboxID}/forkendpoint (e2b-dev/runtime#3202): checkpoint a running sandbox in place (briefly paused, snapshotted with full memory state, and resumed — its ID and expiration stay untouched) and bootcountnew sandboxes from that snapshot.SandboxForkRequest/SandboxForkResultschemas and the/sandboxes/{sandboxID}/forkpath (mirroring the infra spec); JS and Python API clients regenerated viamake codegen.sandbox.fork(opts)instance method andSandbox.fork(sandboxId, opts)static method. ReturnsPromise<Array<Sandbox | Error>>— one entry per requested fork, each either a connectedSandboxinstance or anErrordescribing why that fork failed to start (Promise.allSettled-style, matching the per-fork results of the API). Per-fork error codes go through the same code→class mapping as other API errors (extracted fromhandleApiErrorintoapiErrorFromCode), so e.g. a per-fork 429 (sandbox limit) surfaces asRateLimitError.SandboxForkOptsextends the fullConnectionOpts(likeSandboxConnectOpts), soproxy,logger,apiUrl, etc. work with fork-by-ID.timeoutMsdefaults to 5 minutes likecreate/connect;countdefaults to 1 and is validated client-side (InvalidArgumentErrorforcount < 1); a whole-request 404 maps toSandboxNotFoundError(the source sandbox is the missing resource — same semantics aspause/connect/setTimeout), carrying the API error message when present; per-fork 404 error codes map to genericNotFoundError(the missing resource is fork-internal, e.g. the snapshot).sandbox.fork(timeout=..., count=...)/Sandbox.fork(sandbox_id, ...)and theAsyncSandboxequivalents (same@class_method_variantinstance/static pattern asconnect/pause), returningList[Union[Sandbox, Exception]]. Per-fork errors map through the sharedapi_exception_from_code(extracted fromhandle_api_exception).timeoutis in seconds per Python SDK convention; an explicittimeout=0is preserved. Whole-request 404 raisesSandboxNotFoundException; per-fork 404 codes map to genericNotFoundException.e2band@e2b/python-sdk.Usage
JS:
Python (sync / async):
Notes
timeoutMs(milliseconds) to matchSandboxOpts.timeoutMs/SandboxConnectOpts.timeoutMs; the API receives seconds viatimeoutToSecondsas elsewhere.Test plan
pnpm run format,pnpm run lint,pnpm run typecheckpass at the repo root (tydiagnostics identical to baseline)count < 1→InvalidArgumentError/InvalidArgumentExceptionin JS, Python sync, and Python async;handleApiErrorsuite passes after theapiErrorFromCodeextraction (plus a behavior-parity check of the Pythonhandle_api_exceptionrefactor)SandboxNotFoundError) are written but currently fail against prod with 404 because the fork endpoint (feat(api): add sandbox fork endpoint runtime#3202) is not deployed yet — they should pass once it lands.🤖 Generated with Claude Code