From 8d8348212c7af14201285756197769bcabfc36b1 Mon Sep 17 00:00:00 2001 From: fischerxz Date: Tue, 21 Apr 2026 16:49:06 -0700 Subject: [PATCH 1/5] chore: add .worktrees to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7ccb79cdee..2ffe367001 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ vale-styles .vercel .vscode .DS_Store +.worktrees # Logs logs From ba584c106addd38744f07e6339b44cecb7f6d2c3 Mon Sep 17 00:00:00 2001 From: fischerxz Date: Tue, 21 Apr 2026 16:56:56 -0700 Subject: [PATCH 2/5] fix(js-sdk): forward name param and expose names in createSnapshot Adds SnapshotCreateOpts with an optional name field, threads it through to the POST body, and adds names[] to SnapshotInfo so callers can create named snapshots and read back their assigned aliases. Also fixes SnapshotPaginator.nextItems() which had the same names omission. Fixes #1249 Co-Authored-By: Claude Sonnet 4.6 --- packages/js-sdk/src/sandbox/index.ts | 4 +-- packages/js-sdk/src/sandbox/sandboxApi.ts | 31 +++++++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index 7384dc7af2..0d159a3f36 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -20,8 +20,8 @@ import { SandboxListOpts, SandboxPaginator, SandboxBetaCreateOpts, - SandboxApiOpts, SnapshotListOpts, + SnapshotCreateOpts, SnapshotInfo, SnapshotPaginator, } from './sandboxApi' @@ -621,7 +621,7 @@ export class Sandbox extends SandboxApi { * const newSandbox = await Sandbox.create(snapshot.snapshotId) * ``` */ - async createSnapshot(opts?: SandboxApiOpts): Promise { + async createSnapshot(opts?: SnapshotCreateOpts): Promise { return await SandboxApi.createSnapshot( this.sandboxId, this.resolveApiOpts(opts) diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index f1bcf86bf2..997bd521cf 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -267,6 +267,22 @@ export interface SnapshotListOpts extends SandboxApiOpts { nextToken?: string } +/** + * Options for creating a snapshot. + */ +export interface SnapshotCreateOpts extends SandboxApiOpts { + /** + * Optional human-readable name for the snapshot template. + * + * If a snapshot template with this name already exists, a new build will be + * assigned to the existing template instead of creating a new one. + * + * The name must be unique within your team. Once assigned, the snapshot can + * be used with {@link Sandbox.create} via `team-slug/name`. + */ + name?: string +} + /** * Information about a snapshot. */ @@ -276,6 +292,13 @@ export interface SnapshotInfo { * Can be used with Sandbox.create() to create a new sandbox from this snapshot. */ snapshotId: string + + /** + * Full namespaced names assigned to this snapshot (e.g. `["team-slug/my-snapshot:default"]`). + * Present when the snapshot was created with a `name` option. + * Use any entry with {@link Sandbox.create} to start a sandbox from this snapshot by name. + */ + names: string[] } /** @@ -687,7 +710,7 @@ export class SandboxApi { */ static async createSnapshot( sandboxId: string, - opts?: SandboxApiOpts + opts?: SnapshotCreateOpts ): Promise { const config = new ConnectionConfig(opts) const client = new ApiClient(config) @@ -698,7 +721,9 @@ export class SandboxApi { sandboxID: sandboxId, }, }, - body: {}, + body: { + ...(opts?.name && { name: opts.name }), + }, signal: config.getSignal(opts?.requestTimeoutMs), }) @@ -713,6 +738,7 @@ export class SandboxApi { return { snapshotId: res.data!.snapshotID, + names: res.data!.names, } } @@ -1038,6 +1064,7 @@ export class SnapshotPaginator extends BasePaginator { return (res.data ?? []).map( (snapshot: components['schemas']['SnapshotInfo']) => ({ snapshotId: snapshot.snapshotID, + names: snapshot.names, }) ) } From e4c19b2786cfb7514d1b8443fe47a7a1b387befc Mon Sep 17 00:00:00 2001 From: fischerxz Date: Tue, 21 Apr 2026 17:09:41 -0700 Subject: [PATCH 3/5] fix(js-sdk): use undefined check when forwarding snapshot name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Truthy check silently drops name: '' — check !== undefined instead so the API can validate/reject an explicit empty string rather than the SDK quietly omitting it. Co-Authored-By: Claude Sonnet 4.6 --- packages/js-sdk/src/sandbox/sandboxApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 997bd521cf..337a67711a 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -722,7 +722,7 @@ export class SandboxApi { }, }, body: { - ...(opts?.name && { name: opts.name }), + ...(opts?.name !== undefined && { name: opts.name }), }, signal: config.getSignal(opts?.requestTimeoutMs), }) From 449dd6bf4e27e50a112bb8acb2aeceba95892e9f Mon Sep 17 00:00:00 2001 From: fischerxz Date: Tue, 21 Apr 2026 17:28:01 -0700 Subject: [PATCH 4/5] fix(python-sdk): forward name param and expose names in create_snapshot Mirrors the JS SDK fix: adds optional name param to create_snapshot / _cls_create_snapshot in both sync and async paths, maps names[] from the API response in SnapshotInfo, and fixes the same omission in both snapshot paginators. Also exports SnapshotCreateOpts from the JS SDK public index and adds a patch changeset for both packages. Co-Authored-By: Claude Sonnet 4.6 --- .changeset/fix-snapshot-name.md | 6 ++++++ packages/js-sdk/src/index.ts | 1 + packages/python-sdk/e2b/sandbox/sandbox_api.py | 3 +++ packages/python-sdk/e2b/sandbox_async/main.py | 14 ++++++++++---- packages/python-sdk/e2b/sandbox_async/paginator.py | 3 ++- .../python-sdk/e2b/sandbox_async/sandbox_api.py | 9 +++++++-- packages/python-sdk/e2b/sandbox_sync/main.py | 14 ++++++++++---- packages/python-sdk/e2b/sandbox_sync/paginator.py | 3 ++- .../python-sdk/e2b/sandbox_sync/sandbox_api.py | 9 +++++++-- 9 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 .changeset/fix-snapshot-name.md diff --git a/.changeset/fix-snapshot-name.md b/.changeset/fix-snapshot-name.md new file mode 100644 index 0000000000..854f495d96 --- /dev/null +++ b/.changeset/fix-snapshot-name.md @@ -0,0 +1,6 @@ +--- +"e2b": patch +"@e2b/python-sdk": patch +--- + +Forward optional `name` parameter in `createSnapshot` / `create_snapshot` and return `names` array in `SnapshotInfo` for both JS and Python SDKs. diff --git a/packages/js-sdk/src/index.ts b/packages/js-sdk/src/index.ts index e67092a01c..fbf1ba7d91 100644 --- a/packages/js-sdk/src/index.ts +++ b/packages/js-sdk/src/index.ts @@ -62,6 +62,7 @@ export type { SandboxInfoLifecycle, SnapshotInfo, SnapshotListOpts, + SnapshotCreateOpts, SnapshotPaginator, } from './sandbox/sandboxApi' diff --git a/packages/python-sdk/e2b/sandbox/sandbox_api.py b/packages/python-sdk/e2b/sandbox/sandbox_api.py index 96e4d46271..9bf5784469 100644 --- a/packages/python-sdk/e2b/sandbox/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox/sandbox_api.py @@ -297,6 +297,9 @@ class SnapshotInfo: snapshot_id: str """Snapshot identifier — template ID with tag, or namespaced name with tag (e.g. my-snapshot:latest). Can be used with Sandbox.create() to create a new sandbox from this snapshot.""" + names: List[str] = field(default_factory=list) + """Full namespaced names assigned to this snapshot (e.g. ["team-slug/my-snapshot:default"]). Present when the snapshot was created with a name. Use any entry with Sandbox.create() to start a sandbox from this snapshot by name.""" + class PaginatorBase: def __init__( diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py index 0fe175de3d..1edaae72a5 100644 --- a/packages/python-sdk/e2b/sandbox_async/main.py +++ b/packages/python-sdk/e2b/sandbox_async/main.py @@ -694,6 +694,7 @@ async def beta_pause( @overload async def create_snapshot( self, + name: Optional[str] = None, **opts: Unpack[ApiParams], ) -> SnapshotInfo: """ @@ -705,7 +706,8 @@ async def create_snapshot( Use the returned `snapshot_id` with `AsyncSandbox.create(snapshot_id)` to create a new sandbox from the snapshot. - :return: Snapshot information including the snapshot ID + :param name: Optional human-readable name for the snapshot template. If a snapshot with this name already exists, a new build is assigned to it instead of creating a new one. + :return: Snapshot information including the snapshot ID and names """ ... @@ -713,6 +715,7 @@ async def create_snapshot( @staticmethod async def create_snapshot( sandbox_id: str, + name: Optional[str] = None, **opts: Unpack[ApiParams], ) -> SnapshotInfo: """ @@ -721,14 +724,15 @@ async def create_snapshot( The sandbox will be paused while the snapshot is being created. :param sandbox_id: Sandbox ID - - :return: Snapshot information including the snapshot ID + :param name: Optional human-readable name for the snapshot template. + :return: Snapshot information including the snapshot ID and names """ ... @class_method_variant("_cls_create_snapshot") async def create_snapshot( self, + name: Optional[str] = None, **opts: Unpack[ApiParams], ) -> SnapshotInfo: """ @@ -740,10 +744,12 @@ async def create_snapshot( Use the returned `snapshot_id` with `AsyncSandbox.create(snapshot_id)` to create a new sandbox from the snapshot. - :return: Snapshot information including the snapshot ID + :param name: Optional human-readable name for the snapshot template. If a snapshot with this name already exists, a new build is assigned to it instead of creating a new one. + :return: Snapshot information including the snapshot ID and names """ return await SandboxApi._cls_create_snapshot( sandbox_id=self.sandbox_id, + name=name, **self.connection_config.get_api_params(**opts), ) diff --git a/packages/python-sdk/e2b/sandbox_async/paginator.py b/packages/python-sdk/e2b/sandbox_async/paginator.py index de6d31fb63..f3db7ddd2e 100644 --- a/packages/python-sdk/e2b/sandbox_async/paginator.py +++ b/packages/python-sdk/e2b/sandbox_async/paginator.py @@ -121,5 +121,6 @@ async def next_items(self) -> List[SnapshotInfo]: raise SandboxException(f"{res.parsed.message}: Request failed") return [ - SnapshotInfo(snapshot_id=snapshot.snapshot_id) for snapshot in res.parsed + SnapshotInfo(snapshot_id=snapshot.snapshot_id, names=list(snapshot.names)) + for snapshot in res.parsed ] diff --git a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py index 8946b038fa..676c73d0fd 100644 --- a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py @@ -297,6 +297,7 @@ async def _cls_get_metrics( async def _cls_create_snapshot( cls, sandbox_id: str, + name: Optional[str] = None, **opts: Unpack[ApiParams], ) -> SnapshotInfo: config = ConnectionConfig(**opts) @@ -305,7 +306,9 @@ async def _cls_create_snapshot( res = await post_sandboxes_sandbox_id_snapshots.asyncio_detailed( sandbox_id, client=api_client, - body=PostSandboxesSandboxIDSnapshotsBody(), + body=PostSandboxesSandboxIDSnapshotsBody( + name=name if name is not None else UNSET, + ), ) if res.status_code == 404: @@ -320,7 +323,9 @@ async def _cls_create_snapshot( if isinstance(res.parsed, Error): raise SandboxException(f"{res.parsed.message}: Request failed") - return SnapshotInfo(snapshot_id=res.parsed.snapshot_id) + return SnapshotInfo( + snapshot_id=res.parsed.snapshot_id, names=list(res.parsed.names) + ) @classmethod async def _cls_delete_snapshot( diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index a52cb4c303..093c251593 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -691,6 +691,7 @@ def beta_pause( @overload def create_snapshot( self, + name: Optional[str] = None, **opts: Unpack[ApiParams], ) -> SnapshotInfo: """ @@ -702,7 +703,8 @@ def create_snapshot( Use the returned `snapshot_id` with `Sandbox.create(snapshot_id)` to create a new sandbox from the snapshot. - :return: Snapshot information including the snapshot ID + :param name: Optional human-readable name for the snapshot template. If a snapshot with this name already exists, a new build is assigned to it instead of creating a new one. + :return: Snapshot information including the snapshot ID and names """ ... @@ -710,6 +712,7 @@ def create_snapshot( @staticmethod def create_snapshot( sandbox_id: str, + name: Optional[str] = None, **opts: Unpack[ApiParams], ) -> SnapshotInfo: """ @@ -718,14 +721,15 @@ def create_snapshot( The sandbox will be paused while the snapshot is being created. :param sandbox_id: Sandbox ID - - :return: Snapshot information including the snapshot ID + :param name: Optional human-readable name for the snapshot template. + :return: Snapshot information including the snapshot ID and names """ ... @class_method_variant("_cls_create_snapshot") def create_snapshot( self, + name: Optional[str] = None, **opts: Unpack[ApiParams], ) -> SnapshotInfo: """ @@ -737,10 +741,12 @@ def create_snapshot( Use the returned `snapshot_id` with `Sandbox.create(snapshot_id)` to create a new sandbox from the snapshot. - :return: Snapshot information including the snapshot ID + :param name: Optional human-readable name for the snapshot template. If a snapshot with this name already exists, a new build is assigned to it instead of creating a new one. + :return: Snapshot information including the snapshot ID and names """ return SandboxApi._cls_create_snapshot( sandbox_id=self.sandbox_id, + name=name, **self.connection_config.get_api_params(**opts), ) diff --git a/packages/python-sdk/e2b/sandbox_sync/paginator.py b/packages/python-sdk/e2b/sandbox_sync/paginator.py index 0a5649052b..22ed627124 100644 --- a/packages/python-sdk/e2b/sandbox_sync/paginator.py +++ b/packages/python-sdk/e2b/sandbox_sync/paginator.py @@ -121,5 +121,6 @@ def next_items(self) -> List[SnapshotInfo]: raise SandboxException(f"{res.parsed.message}: Request failed") return [ - SnapshotInfo(snapshot_id=snapshot.snapshot_id) for snapshot in res.parsed + SnapshotInfo(snapshot_id=snapshot.snapshot_id, names=list(snapshot.names)) + for snapshot in res.parsed ] diff --git a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py index 4cad1247dc..f3bcb5f863 100644 --- a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py @@ -324,6 +324,7 @@ def _cls_connect( def _cls_create_snapshot( cls, sandbox_id: str, + name: Optional[str] = None, **opts: Unpack[ApiParams], ) -> SnapshotInfo: config = ConnectionConfig(**opts) @@ -332,7 +333,9 @@ def _cls_create_snapshot( res = post_sandboxes_sandbox_id_snapshots.sync_detailed( sandbox_id, client=api_client, - body=PostSandboxesSandboxIDSnapshotsBody(), + body=PostSandboxesSandboxIDSnapshotsBody( + name=name if name is not None else UNSET, + ), ) if res.status_code == 404: @@ -347,7 +350,9 @@ def _cls_create_snapshot( if isinstance(res.parsed, Error): raise SandboxException(f"{res.parsed.message}: Request failed") - return SnapshotInfo(snapshot_id=res.parsed.snapshot_id) + return SnapshotInfo( + snapshot_id=res.parsed.snapshot_id, names=list(res.parsed.names) + ) @classmethod def _cls_delete_snapshot( From 283703f5493d69cb8f017e905ea843eb6e488b17 Mon Sep 17 00:00:00 2001 From: fischerxz Date: Tue, 21 Apr 2026 17:38:16 -0700 Subject: [PATCH 5/5] fix(python-sdk): drop list() copy and guard names against None snapshot.names / res.parsed.names is already list[str] from the generated client, so list() was an unnecessary O(n) copy. Replace with `or []` to also guard against None for snapshots created before the names field existed. Co-Authored-By: Claude Sonnet 4.6 --- packages/python-sdk/e2b/sandbox_async/paginator.py | 2 +- packages/python-sdk/e2b/sandbox_async/sandbox_api.py | 2 +- packages/python-sdk/e2b/sandbox_sync/paginator.py | 2 +- packages/python-sdk/e2b/sandbox_sync/sandbox_api.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/python-sdk/e2b/sandbox_async/paginator.py b/packages/python-sdk/e2b/sandbox_async/paginator.py index f3db7ddd2e..2b84f41e0e 100644 --- a/packages/python-sdk/e2b/sandbox_async/paginator.py +++ b/packages/python-sdk/e2b/sandbox_async/paginator.py @@ -121,6 +121,6 @@ async def next_items(self) -> List[SnapshotInfo]: raise SandboxException(f"{res.parsed.message}: Request failed") return [ - SnapshotInfo(snapshot_id=snapshot.snapshot_id, names=list(snapshot.names)) + SnapshotInfo(snapshot_id=snapshot.snapshot_id, names=snapshot.names or []) for snapshot in res.parsed ] diff --git a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py index 676c73d0fd..bc64026d35 100644 --- a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py @@ -324,7 +324,7 @@ async def _cls_create_snapshot( raise SandboxException(f"{res.parsed.message}: Request failed") return SnapshotInfo( - snapshot_id=res.parsed.snapshot_id, names=list(res.parsed.names) + snapshot_id=res.parsed.snapshot_id, names=res.parsed.names or [] ) @classmethod diff --git a/packages/python-sdk/e2b/sandbox_sync/paginator.py b/packages/python-sdk/e2b/sandbox_sync/paginator.py index 22ed627124..260b016eca 100644 --- a/packages/python-sdk/e2b/sandbox_sync/paginator.py +++ b/packages/python-sdk/e2b/sandbox_sync/paginator.py @@ -121,6 +121,6 @@ def next_items(self) -> List[SnapshotInfo]: raise SandboxException(f"{res.parsed.message}: Request failed") return [ - SnapshotInfo(snapshot_id=snapshot.snapshot_id, names=list(snapshot.names)) + SnapshotInfo(snapshot_id=snapshot.snapshot_id, names=snapshot.names or []) for snapshot in res.parsed ] diff --git a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py index f3bcb5f863..000f1576f6 100644 --- a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py @@ -351,7 +351,7 @@ def _cls_create_snapshot( raise SandboxException(f"{res.parsed.message}: Request failed") return SnapshotInfo( - snapshot_id=res.parsed.snapshot_id, names=list(res.parsed.names) + snapshot_id=res.parsed.snapshot_id, names=res.parsed.names or [] ) @classmethod