## Summary
Implements Secrets Management in the SDK per the [Secrets Vault SDK
proposal](https://app.notion.com/p/3bab8c29687380b6a8f3e2ecae3f1b50) and
the backend Secrets API. Linear:
[SDK-133](https://linear.app/e2b/issue/SDK-133/sdk-for-managing-secrets).
Docs: [e2b-dev/docs#379](https://github.com/e2b-dev/docs/pull/379).
Spec sync: bumps `spec/infra-ref` to `e19a12b8` (the commit that adds
the Secrets API), adds the `secrets` tag to the `redocly.yaml` filters,
and regenerates via `make codegen` (the regen also pulls in unrelated
upstream spec updates, e.g. the `Error.errorCode` field). The js-sdk
envd schema generation now bundles through a new `envd` redocly api that
filters out operations the upstream spec marks `x-internal: true`
(orchestrator control plane: `/init`, `/freeze`, `/unfreeze`,
`/collapse`, `/fsfreeze`, `/fsthaw`) plus their now-unused component
schemas, so they no longer appear in `src/envd/schema.gen.ts`.
The existing `Secret` class (previously only the `iamToken`/`iam_token`
workload-identity helper) becomes the secrets management surface,
equivalent across JS, sync Python (`Secret`), and async Python
(`AsyncSecret`):
```typescript
Secret.create(name, value, opts?): Promise<SecretInfo> // POST /secrets
Secret.update(secret, value, opts?): Promise<SecretInfo> // POST /secrets/{secretID} (rotates to a new version)
Secret.getInfo(secret, opts?): Promise<SecretInfo> // GET /secrets/{secretID}
Secret.list(opts?): SecretPaginator // GET /secrets (cursor-paginated)
Secret.exists(secret, opts?): Promise<boolean> // 200 → true, 404 → false
Secret.destroy(secret, opts?): Promise<boolean> // 204 → true, 404 → false
Secret.fill(secret): string // local marker formatting, no network call
```
Design decisions per the proposal:
- **Values are write-only**: `SecretInfo` carries only metadata
(`secretId`, `name`, `version`, `metadata`, `createdAt`, `updatedAt`);
no read surface or error message includes a value.
- `update`/`getInfo` throw `SecretNotFoundError` /
`SecretNotFoundException` on 404 (subclass of `NotFoundError` /
`NotFoundException`, so generic not-found catches keep working; general
failures throw the new `SecretError` / `SecretException`);
`exists`/`destroy` map 404 to `false` instead.
- `secret` selector accepts either the `sec_` ID or the canonical
lowercase name (backend resolves both).
- `fill` returns the `${e2b.secrets.name}` marker for use in a network
rule's request transform — always the current version, purely local. The
egress proxy replaces the marker with the secret's current value when it
forwards a matching request; unresolvable markers fail open (the request
is forwarded with the affected headers omitted).
- Version-management endpoints from the proposal are marked TBD and not
in the committed backend contract, so they are intentionally not
implemented.
Python moves `e2b/secret.py` to an `e2b/secret/` package (`base.py`
shares `fill`/`iam_token`, `secret_sync.py` / `secret_async.py` mirror
each other); `from e2b import Secret` is unchanged.
Usage:
```typescript
import { Sandbox, Secret } from 'e2b'
const info = await Secret.create('stripe_api_key', 'sk_live_...', { metadata: { env: 'prod' } })
await Secret.update('stripe_api_key', 'sk_live_new...') // rotate → version 2
// Inject into matching outbound requests via a network rule's transform:
const sandbox = await Sandbox.create({
network: {
allowOut: ({ rules }) => [...rules.keys()],
denyOut: ({ allTraffic }) => [allTraffic],
rules: {
'api.stripe.com': [
{
transform: {
headers: { Authorization: `Bearer ${Secret.fill('stripe_api_key')}` },
},
},
],
},
},
})
await Secret.destroy('stripe_api_key')
```
```python
from e2b import AsyncSecret
info = await AsyncSecret.create("stripe_api_key", "sk_live_...", metadata={"env": "prod"})
paginator = AsyncSecret.list(limit=100)
while paginator.has_next:
secrets = await paginator.next_items()
print(AsyncSecret.fill("stripe_api_key")) # ${e2b.secrets.stripe_api_key}
```
Tests: msw-mocked JS suite (`tests/secret/secret.test.ts`) and
monkeypatched sync/async Python suites covering CRUD, pagination, 404
semantics, and `fill`. `pnpm run format/lint/typecheck` pass; changeset
included (minor for `e2b` and `@e2b/python-sdk`).
Link to Devin session:
https://app.devin.ai/sessions/175095f75cbe42df8710718a1ff2a6a3
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>
Drafts the SDK surface for [bring your own
proxy](https://e2b-docs-byop-egress-proxy.mintlify.site/network/byop):
`network.egressProxy` / `network["egress_proxy"]` on sandbox create, on
`updateNetwork` / `update_network`, and in what `getInfo` / `get_info`
reports back. Tunneling happens on the host after the allow and deny
lists are evaluated, so nothing runs inside the sandbox and code running
there can neither see the proxy nor route around it.
## The spec pin comes first
The pinned infra spec marked `egressProxy` `x-not-implemented: true`,
which Redocly's `filter-out` decorator drops from both generated clients
— so the field did not exist in `schema.gen.ts` or in the Python client
models, and no handwritten surface could reach it.
[infra@0716edb9e8](https://github.com/e2b-dev/infra/commit/0716edb9e840f110c5f87c186876c01e61553098)
removes the flag, so the first commit bumps `spec/infra-ref` and re-runs
codegen rather than hand-writing the wire types.
The pin picks up three other spec changes, and all of them are invisible
to the SDKs: `AdminTeamRunningSandboxCounts`, the dead
`NodeDetail.cachedBuilds` field, and `/admin/sandboxes/running-counts`
are admin-tagged, and the envd spec is byte-identical between the two
commits (verified by comparing the `packages/envd/spec` trees at both
refs). `make codegen` could not run here because the VM has no Docker,
so the spec was replaced with the byte-identical upstream file at the
new pin and the two REST generators were run natively with the pinned
`@redocly/cli` and `e2b-openapi-python-client`.
## Usage
Create a sandbox that tunnels its egress:
```ts
import { Sandbox } from 'e2b'
const sandbox = await Sandbox.create({
network: {
egressProxy: {
address: 'proxy.example.com:1080',
username: 'proxy-user',
password: 'proxy-password',
},
},
})
```
```python
from e2b import Sandbox
sandbox = Sandbox.create(
network={
"egress_proxy": {
"address": "proxy.example.com:1080",
"username": "proxy-user",
"password": "proxy-password",
},
},
)
```
It composes with the rest of the network configuration — here everything
except `api.example.com` is denied, and what is allowed goes through
your proxy:
```ts
await Sandbox.create({
network: {
allowOut: ['api.example.com'],
denyOut: ({ allTraffic }) => [allTraffic],
egressProxy: { address: 'proxy.example.com:1080' },
},
})
```
```python
Sandbox.create(
network={
"allow_out": ["api.example.com"],
"deny_out": lambda ctx: [ctx.all_traffic],
"egress_proxy": {"address": "proxy.example.com:1080"},
},
)
```
Set or replace it on a sandbox that is already running, with no restart.
The update replaces the whole configuration instead of merging into it,
so an update that leaves the proxy out stops tunneling:
```ts
await sandbox.updateNetwork({
allowOut: ['api.example.com'],
denyOut: ({ allTraffic }) => [allTraffic],
egressProxy: { address: 'proxy.example.com:1080' },
})
// Stop tunneling: an update without egressProxy clears it
await sandbox.updateNetwork({})
```
```python
sandbox.update_network({
"allow_out": ["api.example.com"],
"deny_out": lambda ctx: [ctx.all_traffic],
"egress_proxy": {"address": "proxy.example.com:1080"},
})
# Stop tunneling: an update without egress_proxy clears it
sandbox.update_network({})
```
Read the active proxy back:
```ts
const info = await sandbox.getInfo()
console.log(info.network?.egressProxy)
// { address: 'proxy.example.com:1080', username: 'proxy-user' }
```
```python
info = sandbox.get_info()
print(info.network["egress_proxy"])
# {'address': 'proxy.example.com:1080', 'username': 'proxy-user'}
```
## Design notes
- **`SandboxEgressProxyOpts` in, `SandboxEgressProxyInfo` out.** The API
never returns the password, so the result type does not have the field —
the same split as `SandboxNetworkRule` / `SandboxNetworkRuleInfo`.
`fromApiEgressProxy` / `_from_client_egress_proxy` map the generated
type at the boundary and drop a password even if a future API version
starts echoing one back, so the type cannot quietly become a lie.
- **The body is rebuilt from known fields**, as `buildIamBody` already
does, so stray keys on the caller's object never reach the wire and a
later mutation of it cannot alter an in-flight request.
- **No client-side validation.** Address form, port range, hostname
resolution, the internal-range rejection and the
password-without-username rule are all the server's — it is the only
side that can check them, and each already comes back as a readable API
error.
- **`null` never reaches a consumer.** The wire field is nullable; both
SDKs normalize it (absent key in Python, `undefined` in JS), and an
explicit `null` / `None` from an untyped caller is treated as "no proxy"
on the way in.
- Both types are exported from the flat entry points (`index.ts`,
`__all__`).
## Testing
Unit-level in both SDKs — msw in JS (12 tests), the shared builders in
Python (11 tests, covering sync and async since they share the
builders). Integration coverage is not included on purpose: tunneling
needs a SOCKS5 proxy reachable from E2B's infrastructure, which CI has
no way to stand up, and the feature is gated behind a private-beta team
flag.
`pnpm run format`, `pnpm run lint` and `pnpm run typecheck` are clean
repo-wide. The remaining test failures in this environment are all
`AuthenticationException` / missing `E2B_API_KEY` in pre-existing
integration suites; no credentials were available on the VM.
## Notes
- BYOP is available on E2B Cloud and in BYOC. A sandbox that names a
proxy on a deployment built from open source `e2b-dev/infra` is rejected
as unsupported by the orchestrator, which is why the field carried
`x-not-implemented` upstream for a while.
- No Linear MCP was available in this run, so no issue is linked.
<div><a
href="https://cursor.com/agents/bc-653eef78-87bb-5c9c-92d8-e573cd7ba5be?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a
href="https://cursor.com/automations/8e94ee92-9b0d-11f1-ba66-0e7d0216e441"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/view-automation-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/view-automation-light.png"><img
alt="View Automation" width="141" height="28"
src="https://cursor.com/assets/images/view-automation-dark.png"></picture></a> </div>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Mish Ushakov <mishushakov@users.noreply.github.com>
When a team is connected to a custom (BYOC) cluster, the volume API now
returns that cluster's domain in the create and get responses. The JS
and Python (sync + async) SDKs use this domain as the destination for
volume content requests instead of the default api.<E2B_DOMAIN> host,
falling back to the configured domain when none is returned.
The domain field is read defensively from the response until
spec/infra-ref is bumped to the infra commit that adds it and `make
codegen` regenerates the typed schema.
Claude-Session: https://claude.ai/code/session_01212WCmNz1prPKrjhTv2PDj
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Matt Brockman <matt.brockman@e2b.dev>
The specs in `spec/` were copied from their source repos by hand and had
drifted ~2,400 lines behind infra, so they are now imported with
Copybara (`copy.bara.sky`, run in a pinned Docker image by
`scripts/fetch-spec.sh`): `make codegen` re-fetches them at the commits
pinned in `spec/infra-ref` and `spec/belt-ref` before generating, and
the generated-files CI check fails if the tracked copies don't match the
pins. Regenerating from the current pins picks up the accumulated spec
changes in the generated JS/Python clients (renamed request schemas,
`SandboxNetworkConfig`, `SandboxIam` workload identity,
`FILE_TYPE_SYMLINK`, access-token auth deprecation, volume path-metadata
tweaks). The one handwritten SDK change follows from that: the public
`FileType` enums gain a `SYMLINK` member (JS and both Python surfaces)
so entries envd reports as symlinks show up in `files.list()` and
`getInfo()`/`get_info()` instead of being silently skipped as unknown
types. The custom `spec/remove_extra_tags.py` tag-filtering script is
replaced by Redocly CLI's `filter-in` decorator (`redocly.yaml`), which
produces identical generated JS output; a `filter-out` decorator
additionally drops any operation or component schema the upstream specs
mark `x-not-implemented: true` (currently the SOCKS5
`SandboxEgressProxyConfig`/`egressProxy` surface, which infra flagged as
spec-only); each SDK's bundle now goes to its own gitignored
`spec/openapi_generated.<api>.yml` instead of both pipelines overwriting
one shared file; Python client models now list fields in spec order
instead of alphabetical (mechanical reordering only — construct models
with keyword args). Spec fetches try whatever GitHub token is available
and fall back to the tracked copies with a warning (the public infra
specs also fetch anonymously); in CI a short-lived belt-scoped token is
minted from the org-wide Autofixer GitHub App (no new secrets), so fork
PRs simply fall back for the belt spec; the CI workflows also cache the
Copybara image alongside the codegen image, and the previously ignored
`CODEGEN_IMAGE` env is honored by the Makefile.
## Usage
```sh
# update the specs: bump a pin, then regenerate
echo <infra-commit-sha> > spec/infra-ref
make codegen
# fetch a single spec without regenerating
pnpm fetch:api-spec # spec/openapi.yml from infra
pnpm fetch:envd-spec # spec/envd/ from infra
pnpm fetch:volume-spec # spec/openapi-volumecontent.yml from belt
# try the latest spec without touching the pin
E2B_INFRA_REF=main pnpm fetch:api-spec
# change which endpoint tags an SDK exposes
$EDITOR redocly.yaml && make codegen
```
```ts
// symlinks are now visible in the filesystem API (JS; same shape in Python)
const entries = await sandbox.files.list('/home/user')
const link = entries.find((e) => e.type === FileType.SYMLINK)
console.log(link?.symlinkTarget)
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Summary
Adds SDK support for the new `POST /sandboxes/{sandboxID}/fork` endpoint
(e2b-dev/infra#3202): checkpoint a running sandbox in place (briefly
paused, snapshotted with full memory state, and resumed — its ID and
expiration stay untouched) and boot `count` new sandboxes from that
snapshot.
- **spec**: adds `SandboxForkRequest` / `SandboxForkResult` schemas and
the `/sandboxes/{sandboxID}/fork` path (mirroring the infra spec); JS
and Python API clients regenerated via `make codegen`.
- **js-sdk**: `sandbox.fork(opts)` instance method and
`Sandbox.fork(sandboxId, opts)` static method. Returns
`Promise<Array<Sandbox | Error>>` — one entry per requested fork, each
either a connected `Sandbox` instance or an `Error` describing 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 from `handleApiError` into
`apiErrorFromCode`), so e.g. a per-fork 429 (sandbox limit) surfaces as
`RateLimitError`. `SandboxForkOpts` extends the full `ConnectionOpts`
(like `SandboxConnectOpts`), so `proxy`, `logger`, `apiUrl`, etc. work
with fork-by-ID. `timeoutMs` defaults to 5 minutes like
`create`/`connect`; `count` defaults to 1 and is validated client-side
(`InvalidArgumentError` for `count < 1`); a whole-request 404 maps to
`SandboxNotFoundError` (the source sandbox is the missing resource —
same semantics as `pause`/`connect`/`setTimeout`), carrying the API
error message when present; per-fork 404 error codes map to generic
`NotFoundError` (the missing resource is fork-internal, e.g. the
snapshot).
- **python-sdk**: `sandbox.fork(timeout=..., count=...)` /
`Sandbox.fork(sandbox_id, ...)` and the `AsyncSandbox` equivalents (same
`@class_method_variant` instance/static pattern as `connect`/`pause`),
returning `List[Union[Sandbox, Exception]]`. Per-fork errors map through
the shared `api_exception_from_code` (extracted from
`handle_api_exception`). `timeout` is in seconds per Python SDK
convention; an explicit `timeout=0` is preserved. Whole-request 404
raises `SandboxNotFoundException`; per-fork 404 codes map to generic
`NotFoundException`.
- **changesets**: minor bumps for `e2b` and `@e2b/python-sdk`.
## Usage
JS:
```ts
const sandbox = await Sandbox.create()
const [fork1, fork2] = await sandbox.fork({ count: 2, timeoutMs: 60_000 })
if (fork1 instanceof Sandbox) {
await fork1.commands.run('echo "hello from fork"')
}
// or by ID
const forks = await Sandbox.fork(sandbox.sandboxId, { count: 2 })
```
Python (sync / async):
```python
sandbox = Sandbox.create()
fork1, fork2 = sandbox.fork(count=2, timeout=60)
if isinstance(fork1, Sandbox):
fork1.commands.run('echo "hello from fork"')
# or by ID
forks = Sandbox.fork(sandbox.sandbox_id, count=2)
```
```python
sandbox = await AsyncSandbox.create()
fork1, fork2 = await sandbox.fork(count=2)
```
## Notes
- The JS option is named `timeoutMs` (milliseconds) to match
`SandboxOpts.timeoutMs` / `SandboxConnectOpts.timeoutMs`; the API
receives seconds via `timeoutToSeconds` as elsewhere.
- Failed forks are returned as error **values** in the array rather than
rejected promises, so a partial failure doesn't throw away the
successful forks and there are no unhandled-rejection hazards. A
per-fork error message includes the API error code only when the API
returned one.
## Test plan
- [x] `pnpm run format`, `pnpm run lint`, `pnpm run typecheck` pass at
the repo root (`ty` diagnostics identical to baseline)
- [x] Offline tests pass: `count < 1` → `InvalidArgumentError` /
`InvalidArgumentException` in JS, Python sync, and Python async;
`handleApiError` suite passes after the `apiErrorFromCode` extraction
(plus a behavior-parity check of the Python `handle_api_exception`
refactor)
- [ ] Integration tests (single fork with FS state inheritance +
independence, multi-fork with unique IDs, fork-by-ID, fork of killed
sandbox → `SandboxNotFoundError`) are written but currently fail against
prod with 404 because the fork endpoint (e2b-dev/infra#3202) is not
deployed yet — they should pass once it lands.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Adds an optional `name` filter to `Sandbox.listSnapshots()` /
`Sandbox.list_snapshots()`, mirroring the infra snapshots list endpoint
([e2b-dev/infra#3184](https://github.com/e2b-dev/infra/pull/3184)). The
filter accepts a snapshot name or ID, optionally tag-qualified (e.g.
`"my-snapshot"`, `"my-team/my-snapshot"` or `"my-snapshot:v1"`); unknown
names return an empty list. It's a flat top-level option alongside the
existing `sandboxId` filter (non-breaking) and can be combined with it —
the backend applies both with AND, matching the `metadata`+`state`
behavior of `Sandbox.list()`. Applied equivalently across the OpenAPI
spec, generated clients, and the JS + Python sync/async SDKs, with tests
and a changeset.
## Usage
```ts
// JS/TS
const paginator = Sandbox.listSnapshots({ name: 'my-snapshot' })
const snapshots = await paginator.nextItems()
// combine filters (snapshots from a sandbox matching a name)
Sandbox.listSnapshots({ sandboxId: 'sandbox-id', name: 'my-snapshot' })
```
```python
# Python (sync)
paginator = Sandbox.list_snapshots(name="my-snapshot")
snapshots = paginator.next_items()
# Python (async)
paginator = AsyncSandbox.list_snapshots(name="my-snapshot")
snapshots = await paginator.next_items()
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Filesystem-only auto-pause (`onTimeout` object form)
Adds an object form to the sandbox **lifecycle** `onTimeout`
(`on_timeout` in Python) that controls the snapshot kind taken when a
sandbox auto-pauses on timeout, via `keepMemory` (`keep_memory`).
`onTimeout` now accepts either the existing bare action (`'pause'` /
`'kill'`) or the object form `{ action, keepMemory }`. When `keepMemory`
is `false` (with `action: 'pause'`), a timeout auto-pause takes a
**filesystem-only** snapshot (no memory) instead of a full memory one,
so the sandbox cold-boots (reboots) from disk on resume — losing running
processes and open connections. Defaults to `true` (full memory
snapshot), so existing callers are unaffected. **The bare string form is
unchanged.**
It's the create-time / auto-pause counterpart to the explicit
`pause(keepMemory=false)` from #1465: same `keepMemory` naming, mapped
onto the `autoPauseMemory` create field.
### Type safety
The object form is a **discriminated union** on `action`: `keepMemory`
is only valid with `action: 'pause'`. Pairing it with `action: 'kill'`
is a **compile-time type error** (TS) / static error (`ty`), and is
additionally rejected at runtime (`InvalidArgumentError` /
`InvalidArgumentException`) for untyped callers.
### Behavior & validation
- `keepMemory` only applies to a `pause` action.
- **Incompatible with auto-resume** — auto-resume wakes a paused sandbox
on inbound traffic by restoring its memory snapshot in place; a
filesystem-only snapshot has no memory to restore (resuming cold-boots
it), so it must be resumed explicitly via `connect()`. Combining
`keepMemory: false` with `autoResume` is rejected client-side.
### Usage
```ts
// JS/TS — filesystem-only auto-pause on timeout
const sbx = await Sandbox.create({
lifecycle: { onTimeout: { action: 'pause', keepMemory: false } },
})
// bare string form still works (full memory snapshot)
const sbx2 = await Sandbox.create({ lifecycle: { onTimeout: 'pause' } })
```
```python
# Python
sbx = Sandbox.create(
lifecycle={"on_timeout": {"action": "pause", "keep_memory": False}}
)
```
### Changes
- `spec/openapi.yml`: `autoPauseMemory` on the create body (+
regenerated JS/Python clients).
- JS `SandboxOnTimeout` discriminated union (`'pause' | 'kill' | {
action: 'pause'; keepMemory? } | { action: 'kill' }`) and the Python
`SandboxOnTimeoutPause` / `SandboxOnTimeoutKill` TypedDicts, wired
through `createSandbox` / `_create_sandbox` (sync + async) to
`autoPauseMemory`, with the client-side guards.
- Tests: payload serialization + validation (offline, incl. the `action:
'kill'` type/runtime guard) and live cold-boot e2e in both SDKs;
changeset (`e2b` + `@e2b/python-sdk`, minor).
### Backend dependency
The live e2e tests exercise the real auto-pause→cold-boot path and
require the infra-side `autoPauseMemory` support (e2b-dev/infra#3055),
now merged and deployed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: Babis Chalios <babis.chalios@e2b.dev>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Summary
Adds an optional **`memory`** flag to `pause` in both the JS and Python
SDKs. When `memory` is `false`, the pause captures **only the
filesystem** (no memory snapshot); resuming such a snapshot **cold-boots
(reboots)** the sandbox from disk — losing in-memory state, running
processes, and open connections. Defaults to `true` (full memory
snapshot), so existing callers are unaffected.
This is the SDK surface for the filesystem-only snapshot feature on the
infra side.
## Usage
```ts
// JS / TS
const sbx = await Sandbox.create()
await sbx.pause({ memory: false }) // filesystem-only snapshot
const resumed = await sbx.connect() // resumes by cold-booting from disk
```
```python
# Python (sync)
sbx = Sandbox()
sbx.pause(memory=False) # filesystem-only snapshot
resumed = sbx.connect() # resumes by cold-booting from disk
# Python (async)
sbx = await AsyncSandbox.create()
await sbx.pause(memory=False)
resumed = await sbx.connect()
```
`memory` defaults to `true` — `pause()` / `pause({})` behave exactly as
before.
## What changed
- **spec**: optional `memory: boolean` (default `true`) on `POST
/sandboxes/{sandboxID}/pause` (`SandboxPauseRequest`); both API clients
regenerated via `make codegen`.
- **JS**: `Sandbox.pause` / `betaPause` accept `{ memory }` →
`SandboxApi.pause` sends the request body.
- **Python**: `pause(memory=...)` / `beta_pause` → `_cls_pause` (sync +
async) sends `SandboxPauseRequest(memory=...)`.
- **Tests**: filesystem-only pause+resume reboots the guest while the
filesystem survives — JS (`tests/sandbox/snapshot.test.ts`) and Python
sync + async. All pass against a local stack; `format` / `lint` /
`typecheck` clean.
- **Changeset**: `minor` for `e2b` and `@e2b/python-sdk`.
## Note (related infra observation, not addressed here)
While testing, a filesystem-only **resume cold-boots into a different
default exec context** (`root` / `/root`) than a memory resume (`user` /
`/home/user`). The filesystem itself is fully intact; tests use absolute
paths to be robust to this. Worth confirming on the infra reboot path
whether the template's default user should be restored after a cold
boot.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: Babis Chalios <babis.chalios@e2b.dev>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Summary
- Sync `spec/openapi.yml` from
[e2b-dev/infra@main](https://github.com/e2b-dev/infra/blob/main/spec/openapi.yml)
and re-run `make codegen`.
- Schema changes surfaced in the generated clients:
`SandboxMetric.memCache` (new required int64 — also exposed on the
public `SandboxMetrics` wrapper in both SDKs), `NodeStatus` gains
`standby`, `TeamUser.email` becomes nullable + deprecated, and `POST
/v3/templates` gains a `403` response.
- Upstream-only spec changes (not generated because the client filters
by tag): new `AuthProviderBearerAuth`/`AuthProviderTeamAuth` security
schemes, new admin endpoints for team API keys, and a `clusterID` query
param on `GET /nodes`.
## Test plan
- [x] \`pnpm run format\`, \`pnpm run lint\`, \`pnpm run typecheck\`
- [x] JS \`tests/sandbox/metrics.test.ts\` against the live API
- [x] Python sync + async \`test_metrics.py\` against the live API
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
extracts the `allow_internet_access`, `lifecycle`, and `network` configs
to the get info responses from the api when present.
Create a sandbox with lifecycle and network rules, check info while
running, pause it, and check info again. Network rules, lifecycle
config, and `allowInternetAccess` all returned while running and paused
```
$ e2b sandbox info xxx --format json
# running
{
"sandboxId": "xxx",
"templateId": "xxx",
"name": "stdin",
"metadata": {},
"allowInternetAccess": true,
"envdVersion": "0.4.3",
"startedAt": "2026-03-19T01:39:56.238Z",
"endAt": "2026-03-19T01:44:56.238Z",
"state": "running",
"cpuCount": 2,
"memoryMB": 1024,
"network": {
"allowOut": ["api.example.com", "cdn.example.com"],
"denyOut": ["0.0.0.0/0"],
"allowPublicTraffic": true
},
"lifecycle": {
"onTimeout": "pause",
"autoResume": true
}
}
# paused
{
"sandboxId": "xxx",
"templateId": "xxx",
"metadata": {},
"allowInternetAccess": true,
"envdVersion": "0.4.3",
"startedAt": "2026-03-19T01:39:56.238Z",
"endAt": "2026-03-19T01:40:27.964Z",
"state": "paused",
"cpuCount": 2,
"memoryMB": 1024,
"network": {
"allowOut": ["api.example.com", "cdn.example.com"],
"denyOut": ["0.0.0.0/0"],
"allowPublicTraffic": true
},
"lifecycle": {
"onTimeout": "pause",
"autoResume": true
}
}
```
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Updates the public `sandbox info` response shape across OpenAPI, JS,
and Python SDKs, which may impact downstream consumers that assume the
previous schema. Risk is moderate since changes are additive/optional
but touch generated models and response mapping logic.
>
> **Overview**
> **Sandbox info responses now include network and lifecycle
configuration when present.** The OpenAPI spec and generated JS schema
extend `SandboxDetail` with `allowInternetAccess`, `network`, and a new
`lifecycle` object (with `SandboxOnTimeout` and `SandboxLifecycle`).
>
> The JS SDK updates `SandboxApi.getFullInfo()` and exported types to
return these fields, introducing `SandboxInfoLifecycle` for info
responses. The Python SDK updates generated client models accordingly,
adds `SandboxLifecycle`/`SandboxOnTimeout` models, and maps
`SandboxDetail.network`/`SandboxDetail.lifecycle` into `SandboxInfo`
(plus exports `SandboxInfoLifecycle`).
>
> A changeset bumps `@e2b/python-sdk` and `e2b` as minor for the
expanded info payload.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
adb22292c08b1db9c8fe60c702f83fee2695af97. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Implements the `lifecycle` prop on `Sandbox.create`, taking over and
deprecating the `beta_pause` functionality.
Currently supports:
- `on_timeout`: `kill` (default) | `pause`. Controls what should happen
to the sandbox when it hits end of life. Pause allows for resuming
- `auto_resume`: False (default) | True. Whether the sandbox should
autoresume on traffic
Intended for additional functionality as we update the backend to
support additional props. Blocked from deploying until the API and
client-proxy are deployed but for pre-approval.
(Meant to be extended later as add more capabilities to the API)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes the API contract and request payload shape for sandbox
auto-resume and alters lifecycle/timeout behavior mapping, which could
break older integrations if backend/client versions are mismatched.
>
> **Overview**
> Adds a new `lifecycle` configuration on `Sandbox.create` (JS + Python)
to control what happens at timeout (`kill` vs `pause`) and whether
paused sandboxes auto-resume on traffic (`auto_resume`).
>
> Deprecates `betaPause`/`beta_pause` and the JS `autoPause` create
option in favor of the new lifecycle semantics, updates connect/pause
call paths accordingly, and expands tests to cover resume-on-connect and
auto-resume behaviors.
>
> Updates the OpenAPI contract and generated clients so `autoResume` is
now an object with an `enabled: boolean` flag (removing the previous
policy enum), and bumps SDK versions via a changeset.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
515f9b7fc13a5ec13db75450e8f6252e3c7bcf03. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Integration tests will fail until the feature is deployed in E2B Cloud
production.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Enables checking template alias availability from both SDKs.
>
> - JS: Implements `checkAliasExists` in `template/buildApi.ts`, exposes
`Template.aliasExists` in `template/index.ts`, adds `AliasExistsOptions`
type and tests
> - Python: Adds `Template.alias_exists` and
`AsyncTemplate.alias_exists` wired to generated
`get_templates_aliases_{alias}` client; includes sync/async tests
> - API: Regenerates schemas/clients to include `GET
/templates/aliases/{alias}`, template build logs endpoint and
parameters, and supporting models (e.g., `TemplateAliasResponse`)
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
4e577fd77888478ac83b66db7537cd2355ea050b. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
<!-- CURSOR_SUMMARY -->
> [!NOTE]
> Adds `mcp` config support for sandbox creation across API, JS, and
Python SDKs, and updates timeout tests to use explicit request timeouts.
>
> - **API/OpenAPI**:
> - Add `McpConfig` schema and `mcp` field to `NewSandbox` in
`spec/openapi.yml` and generated TS `schema.gen.ts`.
> - **JS SDK**:
> - `SandboxApi.createSandbox` sends `mcp` in POST body
(`packages/js-sdk/src/sandbox/sandboxApi.ts`).
> - Types updated to include `components["schemas"]["McpConfig"]`.
> - **Python SDK**:
> - Extend `NewSandbox` model and sandbox create flow to accept/pass
`mcp`; async/sync APIs propagate `mcp` to POST `/sandboxes`.
> - **Tests**:
> - Increase `is_running` request timeouts in timeout tests to reduce
flakiness (JS and Python).
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
04ad4e6d4947e33523b74aba2f703fa1e9c05e72. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
# Description
We can simplify typing and return only SandboxInfo in SDKs as the types
are very similar (there are some extra fields in detail, which we don't
want to expose but we need them e.g. for connect)
# Description
Fixes an issue in generating files in Docker. There has been an
incompatibility of `buf` (version `29.5`) and `protoc-gen-es` (version
`2.2.2`).
I updated `protoc-gen-es@` to `2.6.2`
Also refactored the code a little so it's easier to read
Added a CI pipeline job to check all files are properly generated