Skip to content

V2 SDK (named-sandboxes)#185

Closed
LukeSheard wants to merge 46 commits into
mainfrom
named-sandboxes
Closed

V2 SDK (named-sandboxes)#185
LukeSheard wants to merge 46 commits into
mainfrom
named-sandboxes

Conversation

@LukeSheard
Copy link
Copy Markdown
Collaborator

No description provided.

AndyW22 and others added 30 commits March 3, 2026 17:37
Add the `name` and `snapshotOnShutdown` params which we forward to the
new `v1/sandboxes/named` endpoint. This sets up basic testing of our
endpoint.

Merging to the `named-sandboxes` development branch
Important
---

- This is PR contains the `changeset` required to ship a pre-release
(`beta`) package with a breaking change. Merging the PR will not
automatically create the pre-release tag.
- This PR will be merged to `named-sandboxes`, not the main branch.
- All the changes from `sandbox` package are to make it compile. We are
not yet supporting these changes in the CLI; this PR is only about the
`@vercel/sandbox` package.

Breaking Changes
---

**1:** `Sandbox.get()` - parameter renamed and semantics changed.

- Before: `Sandbox.get({ sandboxId: string })`
- After: `Sandbox.get({ name: string, resume?: boolean }`. The sandbox
will also be automatically resumed if it was stopped.

**2:** `Sandbox.list()` - the return items have changed.

This method returns the list of sandboxes, and we are missing the
following fields:

- `id` (we use `name` instead)
- `requestedAt`: the sandbox does not request a session.
- `status`: it is specific from the session. (WIP - Working on providing
this one)

**3:** `Sandbox.list()` - the pagination has changed.

We are using a cursor based token instead.

- Before, the pagination returned: `{ count: number, next: number |
null, prev: number | null }`
- After, the pagination returns: `{ count: number, next: string | null,
total: number }`

**4:** Auto-resume any operation.

- Before: If a sandbox session was stopped, all operations threw an
error.
- After: If the session is stopped/stopping, operations silently create
a new session and retry.

**5:** Timeout / NetworkPolicy are not from the session

Before, the Timeout / NetworkPolicy parameters where from the session,
and were automatically updated when the methods `extendTimeout()` or
`updateNetworkPolicy()` were called. Now, the returned ones are from the
NamedSandbox (base values to create a sandbox), and do not reflect the
updated values for that session.

**6:** Create - semantics changed

- Before: `Sandbox.create()`
- After: `Sandbox.create({ name: string, snapshotOnShutdown?: boolean
}`. The sandbox will also be automatically persistent by default.

Non-breaking changes
---

### New methods on Sandbox

- currentSession() — returns the underlying Session instance.
- update(params) — PATCH the named sandbox config (resources, runtime,
timeout, networkPolicy).
- delete(opts?) — delete the named sandbox (with optional
preserveSnapshots).
- listSessions(params?) — list all sessions created for this named
sandbox.
- listSnapshots(params?) — list all snapshots belonging to this named
sandbox.

### New getters on Sandbox

- name, snapshotOnShutdown, region, vcpus, memory, runtime
- Aggregate usage metrics: totalEgressBytes, totalIngressBytes,
totalActiveCpuDurationMs, totalDurationMs
- updatedAt

### New query filters

- listSandboxes() now accepts an optional name param to filter by named
sandbox.
- listSnapshots() now accepts an optional name param to filter by named
sandbox.

---------

Co-authored-by: Tom Lienard <tom.lienrd@gmail.com>
Create a public `beta` release for NamedSandboxes. This is a breaking
change, so we are also creating the tag `2.0.0-beta`.

All these changes are still behind the `named-sandboxes` branch and
won't be merged into main until validated and tested.
Fix the beta release pipeline, as we are getting this error:

```
> vercel-sandbox@0.0.0 release /home/runner/work/sandbox/sandbox
> changeset publish --tag beta

🦋  error Releasing under custom tag is not allowed in pre mode
🦋  To resolve this exit the pre mode by running `changeset pre exit`
 ELIFECYCLE  Command failed with exit code 1.
```

To ensure we are always publishing in beta, I've added a previous check
to read the `.changeset/pre.json` and ensure it is a beta.
Add CLI support for the named sandboxes.

Notes: this will be merge on the `named-sandboxes` branch. When this
happens, a new `beta` release will be done for the CLI package.

Breaking Changes
---

1. All commands now take sandbox names instead of IDs.
2. sandbox stop no longer has `rm`/`remove` aliases.
3. sandbox run `--remove-after-use` (or just `--rm`) now permanently
deletes the sandbox.
4. sandbox `cp` path format changed — Remote paths now use
`SANDBOX_NAME:PATH` instead of `SANDBOX_ID:PATH`
5. sandbox list column header renamed — The ID column is now NAME.
6. sandbox snapshots list/get column renamed — SOURCE SANDBOX is now
SOURCE SESSION.

New Features
---

1. New sandbox `rm` / `remove` command — Permanently deletes one or more
sandboxes.
2. New sandbox `sessions list` command (alias `sessions ls`) — Lists all
sessions for a given sandbox.
3. New `sandbox config set` subcommand — Update sandbox configuration in
a single command. Example: `sandbox config set my-sandbox --runtime
node24 --vcpus 2 --timeout 1h`.
4. New `sandbox config get` subcommand — Display the current
configuration of a sandbox.
5. New `sandbox create --name` option — Assign a user-chosen name to a
sandbox at creation time.
6. New `sandbox create --no-snapshot-on-shutdown` flag — Disable
automatic snapshotting when a sandbox shuts down.
7. `sandbox run` now supports resuming — When --name is provided, run
first tries to resume an existing sandbox with that name; otherwise it
creates the sandbox.
8. New `sandbox list --name-prefix` option — Filter sandboxes by name
prefix.
9. New `sandbox list --sort-by` option — Sort sandboxes by createdAt or
name.

Other Changes
---

1. `sandbox config network-policy` is deprecated — Now emits a warning.
Try to debug why NPM publishing is failing.
This PR is a revamp of the update mechanism for CLI/SDK and consolidates
it. With the introduction of named sandboxes, we had two types of
updates:

- Update the sandbox configuration. This will be applied during the next
session.
- Update the current session: only `network-policy` and `extend-timeout`
are supported.

Other notes:
- During the last PR, I did not run `pnpm version:prepare` with the
latest changes and there are modifications in
`packages/sandbox/docs/index.md` from existing code that has already
been merged.

SDK Changes
---

- Renamed the session command from `updateNetworkPolicy` to `update`.
Not a breaking change because this was introduced in the beta. By moving
it to `update` instead of `updateNetworkPolicy`, it gives us room to add
new parameters to update the session from without having to do any
future breaking change.
- Deprecate `updateNetworkPolicy` from the sandbox. This is an existing
method prior to the beta and we are just deprecating it - no breaking
change. Use `update` instead.
- New method `update` that allows you to update `vcpus`, `timeout`,
`network-policy`, `persistent`. In the case of `network-policy`, it will
update both the sandbox and the current session. For the other
parameters, we only support updating the sandbox.

New CLI commands
---

These new commands extend the already existing `sandbox config set
network-policy <sandbox_name> <policy>`:

- `sandbox config list` (inspired from `git config --list`). Lists all
the configuration parameters from a sandbox that can be changed with
`sandbox config set`.
- `sandbox config set vcpus <sandbox_name> <count>`
- `sandbox config set timeout <sandbox_name> <time>`
- `sandbox config set persistent <sandbox_name> <bool>`

For the existing `sandbox config set network-policy <sandbox_name>
<policy>`, we have the same treatment as with the SDK: it updates both
sandbox and session (if any).

Note that we also removed support for both `memory` and `runtime` from
the SDK/CLI.
Automatically set the memory to the scale of 1 CPU = 2048MB of memory in
the sandbox.
Changes:

- Support the `--name` option in `sandbox snapshots list` so that you
can filter snapshots by a sandbox.
- When executing `sandbox run --rm`, do not create the sandbox as
persistent, as it is an ephemeral sandbox that will be deleted after
executing the command.
- Support the `--stop` option in `sandbox run`. We already support the
`--rm` option which deletes the sandbox, but users might want just to
stop the sandbox (e.g. debugging, or they want to reuse it for something
else).
These endpoints have now been migrated to the v2 naming, so we can
update them here. This is updating PATCH for updating a sandbox, and
DELTE for deleting a sandbox
Rebase the following commits from main:

- ac49096 - fix(sdk): handle unhandled promise rejections on logs error
(#82)
- 87d4b64 - Version Packages (#83)

The goal is to keep this branch as frequently updates as possible, to
avoid a big merge conflict on the last day.

---------

Co-authored-by: Tom Lienard <tom.lienrd@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Multiple changes to ensure there is compatibility:

- Do not use any `v1` endpoint; use the `v2` endpoints instead.
- Adapt to these new endpoints (modified requests and responses).
- Rename `sandboxId` to `sessionId`
- Rename `namedSandbox` to `sandbox`
- Rename `sandbox` (old term) to `session`
Improve the documentation and some examples to show-case some of the
most important features from the new release.

---------

Co-authored-by: Tom Lienard <tom.lienrd@gmail.com>
When returning a list with pagination (e.g. sandboxes, sessions,
snapshots) we used to return an object with `json`.

Now, we are automatically unwrapping the `json` and returning the fields
the customer wants to use:
- `pagination`
- the paginated item (`sessions`, `sandboxes`, etc.).
## Fix double-error message bug

Before the fix: 
<img width="1290" height="144" alt="Screenshot 2026-03-17 at 11 31 27"
src="https://github.com/user-attachments/assets/a2034570-bbea-4bb3-a607-bfff1f3e9605"
/>

After the fix:

<img width="1289" height="84" alt="Screenshot 2026-03-17 at 11 31 33"
src="https://github.com/user-attachments/assets/4d22596a-0e49-4dc8-b818-836e71d7a339"
/>

This happened when we used `Listr`: stopping a sandbox, removing a
sandbox or removing a snapshot were affected.

## Other fixes

- Improve the JSDOC for `persistent` field - ensure it is the same we
show when updating the persistence of a sandbox.
- Fix the message we show when stopping a session: we showed: `Stopping
sandbox $sandboxName`, now we show `Stopping active session from
$sandboxName`.
Make the `sandbox session list` command behave the same as `sandbox
list`. This means:

- We are adding support for `all` (with the same aliases as `sandbox
list`)
- We are showing by default the running sessions. If you add `all`, we
will show all the sessions and also additional information (the same one
as in `sandbox list`).

With the changes, here it is what it looks like:

<img width="879" height="57" alt="Screenshot 2026-03-17 at 12 07 26"
src="https://github.com/user-attachments/assets/e547ec9f-b0f9-4040-96f6-84d8528cf970"
/>

<img width="1219" height="63" alt="Screenshot 2026-03-17 at 12 07 35"
src="https://github.com/user-attachments/assets/52851f2b-c26e-421a-ab05-f8a6b750e753"
/>
Fix resume race-condition: ensure that when we call `withResume`
multiple times, we only have one request in-fight.

For example, we were having a race condition and created two active
sessions:

```
❯ sandbox create
  ✅ Sandbox coral-sour-iguana-jroMkL created.
     │ team: marc-codina-enhanced-vtest314
     ╰ project: my-sandbox-app
  ❯ sandbox stop coral-sour-iguana-jroMkL
  ✔ Stopping sandbox coral-sour-iguana-jroMkL
  ❯ sandbox ssh coral-sour-iguana-jroMkL
  $ sh
  ▲ /vercel/sandbox/ sandbox session list coral-sour-iguana-jroMkL
  sh: sandbox: command not found
  ▲ /vercel/sandbox/ sandbox session list coral-sour-iguana-jro^CL
  ▲ /vercel/sandbox/ exit
  exit

  ╰▶ connection to ▲ coral-sour-iguana-jroMkL closed.
  ❯ sandbox sessions list coral-sour-iguana-jroMkL
  ID                                 STATUS    CREATED        MEMORY   VCPUS   RUNTIME   TIMEOUT        DURATION
  sbx_1SMyrcBI54UQSPyRB9wPF6PCqOx2   running   1 minute ago   4096     2       node24    in 4 minutes   -
  sbx_OF2yUnDjZyiYhOaA90JKLu3rUa6c   running   1 minute ago   4096     2       node24    in 4 minutes   -
  sbx_UidxVjezS5n7vB8fc8WkxgtVQgyE   stopped   1 minute ago   4096     2       node24    in 4 minutes   12.029s
```

Root cause of the race-condition:

When doing a `sandbox ssh`, we run two operations concurrently via
Promise.race:

- checkIfServerInstalled() → calls sandbox.runCommand()
- installServerBinary() → calls sandbox.writeFiles()
Now that we have added tag support on the API, we can add tags to the
SDK + CLI:

- Add creating a sandbox with up to 5 tags on SDK + CLI
- List sandboxes, filtering by tags (limit of 1, enforced on API level)

  
<img width="1488" height="61" alt="image"
src="https://github.com/user-attachments/assets/8a0977f0-35c1-445d-bb1b-66d6e4eadaf7"
/>
When we do a `ssh` to a resumed sandbox, it works. But it does not work
when trying to do a `ssh` into a new sandbox and it hanged.

There were two errors:

1. We hanged forever without showing the error. This has been fixed.
2. We could not connect because we did not pass the internal parameters
(routes)

## Testing

We have performed these tests:

```
❯ sandbox create
✅ Sandbox beige-leading-flea-4dExSp created.
   │ team: marc-codina-enhanced-vtest314
   ╰ project: my-sandbox-app

❯ sandbox ssh beige-leading-flea-4dExSp
$ sh
▲ /vercel/sandbox/ echo 'hi'
hi
▲ /vercel/sandbox/ exit
exit
╰▶ connection to ▲ beige-leading-flea-4dExSp closed.

❯ sandbox stop beige-leading-flea-4dExSp
✔ Stopping active session from beige-leading-flea-4dExSp

❯ sandbox ssh beige-leading-flea-4dExSp
$ sh
▲ /vercel/sandbox/ exit
exit
╰▶ connection to ▲ beige-leading-flea-4dExSp closed.
```
Bump the beta version for both `sandbox` and `@vercel/sandbox`.
…102)

Fix bug with resuming sandboxes when reading a file. I have also added
an integration tests to detect if this behavior is broken in the future,
as it follows a different path from the other commands (it is a stream).

TL;DR: we were missing the `parseOrThrow` in this method, and
`withResume()` wrapper checks for an error before resuming (we were
returning the error as string)
Change the pagination so that:

- We support the new cursor-based pagination.
- New pagination does not return count, and the pagination limit is 50,
not 100.
- Support for new `sortOrder` parameter for the SDK, `--sort-order`
parameter for the CLI to be able to sort DESC (default) or ASC.
- Support new sandbox filter `statusUpdatedAt`.
Add ability to update a sandboxes tags with the CLI.

Usage when updating:

```
16:09:18 [I]   ❯ node packages/sandbox/dist/sandbox.mjs config tags amber-expected-guppy-FpAfXF --tag owner=andy --tag env=staging 
✅ Tags updated for sandbox amber-expected-guppy-FpAfXF
   │ owner=andy
   ╰ env=staging
```

Usage on `config list`:

```
FIELD            VALUE
vCPUs            2
Timeout          5 minutes
Persistent       true
Network policy   allow-all
Tags             owner=andy, env=staging
```
When using `--name-prefix`, we currently throw an error because it
requires `--sort-by=name`. QoL improvement to automatically set
`--sort-by=name` when passing in `--name-prefix`, unless `--sort-by` it
set to something else.
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to named-sandboxes,
this PR will be updated.

⚠️⚠️⚠️⚠️⚠️⚠️

`named-sandboxes` is currently in **pre mode** so this branch has
prereleases rather than normal releases. If you want to exit
prereleases, run `changeset pre exit` on `named-sandboxes`.

⚠️⚠️⚠️⚠️⚠️⚠️

# Releases
## sandbox@3.0.0-beta.11

### Patch Changes

- Default to `--sort-by=name` when using `--name-prefix` in `sandbox ls`
([#113](#113))

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tom Lienard <tom.lienrd@gmail.com>
## Summary

Expose the `snapshotExpiration` field through the SDK and CLI, allowing
customers to control snapshot TTL at the sandbox level.

## Changes

- **SDK**: Add `snapshotExpiration` to the Zod schema, API client
(`createSandbox`/`updateSandbox`), `Sandbox` class (params, getter,
`create()`, `update()`), and tests.
- **CLI**: Add `--snapshot-expiration` flag to `sandbox create`/`sandbox
run`, `sandbox config snapshot-expiration` subcommand, and display in
`sandbox config list`. Accepts durations (e.g. `7d`) or `none`/`0` for
no expiration.
…126)

Fix an error where we are failing with a 422 when we resume after a
snapshot. This is because we were not handling the 422 Snapshotting
error properly and we did not retry.
marc-vercel and others added 16 commits April 8, 2026 15:30
Rebasing `named-sandboxes`. Commits that I am moving:

1. cf13a34 — refactor(sdk): build @vercel/sandbox with tsdown dual
outputs (#84)
2. 772989c — Support "use workflow" serialization for Sandbox and
Command (#72)
3. cc74dbf — fix(sandbox): read package.json with fs instead of ESM
import (#119)
4. a6b8ce9 — feat(skill): update beta documentation for default snapshot
expiration (#125)
5. 184cd42 — patch(vercel-sandbox): count length by bytes and not ASCII
for binaries (#127)
6. 451c42e — feat(sandbox): accept string and Uint8Array in writeFiles
content (#128)
7. ad52dec — Version Packages (#122)
8. b91b9e4 — fix(sandbox): initialize API client in Command before
reading output (#130)
9. 28237b8 — refactor(workflow-code-runner): inline Sandbox calls in
workflow function (#129)
10. 0786e18 — Version Packages (#131)
11. 9555162 — fix(sandbox): handle abort signal and early stream close
in runCommand (#135)
12. db4e5f3 — Version Packages (#137)

I had to resolve multiple merge conflicts, specially with the commits 1
and 2. I've also added some tests for them because they were touching
`sandbox.ts` for serializing and deserializing, and in this branch we
moved most of the logic to `session.ts`.

---------

Co-authored-by: Gal Schlezinger <gal@spitfire.co.il>
Co-authored-by: Luke Phillips-Sheard <luke.phillips-sheard@vercel.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Luke PS <LukeSheard@users.noreply.github.com>
Co-authored-by: Malte Ubl <cramforce@users.noreply.github.com>
Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Phil Z <pbzona@users.noreply.github.com>
…andbox.create (#141)

Non-breaking change. Three new integration tests have been added to
ensure that the communication between SDK <-> API works properly with
the new field.

Support a new parameter, `onResume`, for `Sandbox.get()` and
`Sandbox.create()`. This new parameter is a hook with this format:

```
onResume: (sandbox: Sandbox) => Promise<void>
```

This hook **will only** be executed when a new session is created as
part of the resume. So, for example, if I run the following code the
`onResume` hook is not going to be called because we are not creating a
new session (it is a no-op):

```
const sandbox = await Sandbox.create();
await Sandbox.get({ name: sandbox.name, resume: true, onResume: (() => {/* Logic here */}) });
```

But in this case, it will be called:

```
const sandbox = await Sandbox.create(onResume: (() => {/* Logic here */}) });
await sandbox.stop({ blocking: true });

// The sandbox is stopped and we need to create a new session. `onResume` hook will be called.
await sandbox.runCommand({ ... });
```
Allow customers to change the currentSnapshotId a snapshot is pointing
at. This is useful because sandbox names cannot be modified, and
customers might want to reuse the same sandbox name (without deleting
it) and point to another snapshot (e.g. rolling back or moving to
another branch).

Listing the config now shows the new current snapshot ID value:

```
> sandbox config list <sbx_name>
FIELD                 VALUE                            
vCPUs                 2                                
Timeout               5 minutes                        
Persistent            true                             
Network policy        allow-all                        
Snapshot expiration   -                                
Current snapshot      snap_yzMmPCRHbr1V4h8RDufUWfO5yqjo
```

Change current snapshot ID pointer:

```
> sandbox config current-snapshot <sbx_name> snap_yzMmPCRHbr1V4h8RDufUWfO5yqjo
✅ Configuration updated for sandbox turquoise-labour-chinchilla-shekX3
   ╰ current-snapshot: snap_yzMmPCRHbr1V4h8RDufUWfO5yqjo
```

Error handling:

```
> sandbox config current-snapshot <sbx_name> snap_yzMmPCRHbr1V4h8RDufUWfO5yqj1
Snapshot 'snap_yzMmPCRHbr1V4h8RDufUWfO5yqj1' was not found or does not belong to this project.
```
…156)

Rebase from main the latest commits.

```
 ┌─────┬─────────┬────────────────────────────────────────────────────────────────────────────┐
  │  #  │ Commit  │                                   Title                                    │
  ├─────┼─────────┼────────────────────────────────────────────────────────────────────────────┤
  │ 1   │ ac34694 │ chore(sandbox): fix the CLI tests (#139)                                   │
  ├─────┼─────────┼────────────────────────────────────────────────────────────────────────────┤
  │ 2   │ 9dc0ac9 │ perf(sdk): re-use Undici Agent across instances (#143)                     │
  ├─────┼─────────┼────────────────────────────────────────────────────────────────────────────┤
  │ 3   │ 3fbabb9 │ fix(sandbox): add workflow serialization support for Snapshot class (#140) │
  ├─────┼─────────┼────────────────────────────────────────────────────────────────────────────┤
  │ 4   │ 42515e1 │ Add node:fs implementation on Sandbox (#112)                               │
  ├─────┼─────────┼────────────────────────────────────────────────────────────────────────────┤
  │ 5   │ 494c2dd │ fix: smarter fallback team selection for scope inference (#120)            │
  ├─────┼─────────┼────────────────────────────────────────────────────────────────────────────┤
  │ 6   │ 468731a │ chore: fix manually written changeset definition (#148)                    │
  ├─────┼─────────┼────────────────────────────────────────────────────────────────────────────┤
  │ 7   │ ad18eca │ Version Packages (#144)                                                    │
  ├─────┼─────────┼────────────────────────────────────────────────────────────────────────────┤
  │ 8   │ 747a689 │ feat(docs): add clarification for vCPUs on enterprise (#149)               │
  ├─────┼─────────┼────────────────────────────────────────────────────────────────────────────┤
  │ 9   │ a6394a4 │ [packages/sandbox] improve timeout values and hour format (#150)           │
  └─────┴─────────┴────────────────────────────────────────────────────────────────────────────┘
```

---------

Co-authored-by: Tom Lienard <tom.lienrd@gmail.com>
Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: LukePS <LukeSheard@users.noreply.github.com>
Co-authored-by: Gregor Martynus <39992+gr2m@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Gal Schlezinger <gal@spitfire.co.il>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Andy <76787794+AndyW22@users.noreply.github.com>
Before, we supported the parameter `blocking` when stopping a sandbox.
This is being removed as of now, as we will be making the `stop`
blocking by default. By doing that, we achieve:

1. When stopping a sandbox, we obtain the latest sandbox data and we can
update the `sandbox` instance so that it is not outdated.
2. When stopping a sandbox via CLI, we can show the metrics/billing info
directly.

CLI
---

Before:

```
❯ sandbox stop <name>
✔ Stopping active session from <name>
```

After:

```
❯ sandbox stop <name>
✔ Sandbox stopped.
   │ sandbox: jade-rapid-jay-NgZfbj               active cpu: 1.582s  mem: 4096 MB         duration: 14.904s  ingress: 1.3 KB  egress: 1.8 KB
   │ session: sbx_DrZYKV2BTInoINSTFul4zJerZL1a    active cpu: 1.582s  mem: 4096 MB         duration: 14.904s  ingress: 1.3 KB  egress: 1.8 KB
   ╰ snapshot: snap_FpjKuN3QlgQzGTxbYY7dUl8qfAv0  size: 241 MB        expires: in 30 days
```

Note that snapshot information only appears IF the sandbox is persistent
and it automatically created a snapshot on shutdown.

SDK
---

Before:

```
const sandbox = Sandbox.create();

// It does not block 100% nor update the `sandbox` instance with the new data.
await sandbox.stop({ blocking: true });
```

After:

```
const sandbox = Sandbox.create();

// Automatically blocks until the sandbox is stopped and, if persistent = true, snapshotted.
await sandbox.stop();
```
Test fixes:
- Fix the 2 failing tests regarding OIDC by mocking it.
- Fix the 2 failing tests regarding killing a command by expecting a 255
instead of 128 + signal code. There was a regression around one month
ago where we started returning 255 instead of the 128 + signal code. We
can revert back to the previously state if we want to, but for now I am
just ensuring the tests pass.
- Increase the `afterEach` timeout from 10 seconds (default) to 30
seconds. This is due to the `.stop()` step being blocking by default.
Persistent sandboxes have to stop the sandbox and snapshot.

Other improvements:
- Integration tests now create snapshots with an expiration time of 1
day, to ensure they are cleaned up fast.
- Integration tests now always delete the sandboxes, to avoid having
test sandboxes in the accounts where we run the tests.
For the beta package, introduce a new static method:

```
Sandbox.getOrCreate()
```

What it does:

1. If the sandbox has no `name`, just create it and execute the
`onCreate` hook if it exists. Otherwise:
2. If the sandbox exists, just do a `Sandbox.get()` and return it.
3. If the sandbox returns `snapshot_not_found`, it means the sandbox
cannot be resumed because the snapshot was deleted/expired. Delete the
sandbox and create a new one with the **same name**. Execute the
`onCreate` hook if it exists.
4. If the sandbox return `not_found`, create a new sandbox. Execute the
`onCreate` hook if it exists.

Important: this is done at the SDK level. We might iterate in the future
and implement it at the API level, which means we will need only one
requests instead of two.
…es, sessions, snapshots (#163)

Introduce pagination support for the CLI/SDK, so that customers and
agents can iterate over existing sandboxes, snapshots and sessions.

CLI
---

The CLI now supports two new parameters to control the pagination:

- `--limit`
- `--cursor`

Also, in the list commands that support pagination, we are also
returning the `More results: ...` at the end, so that humans and agents
know which commands they should run next if they want to iterate over
the results.

```
> sandboxes list --limit 2
...
More results: sandbox list --limit 2 --cursor eyJvZmZzZXQiOjJ9

> sandbox sessions list my-sandbox --limit 2
...
More results: sandbox sessions list my-sandbox --limit 2 --cursor eyJvZmZzZXQiOjJ9

> sandbox snapshots list --name my-sandbox --limit 2
...
More results: sandbox snapshots list --name my-sandbox --limit 2 --cursor eyJvZmZzZXQiOjJ9
```

SDK
---

SDK introduces a small breaking change for the beta customers. When
executing any of these methods:

- `await Sandbox.list()`
- `await sandbox.listSessions()`
- `await sandbox.listSnapshots()`
- `await Snapshot.list()`

We are going to return an Iterable. To match the previous behavior, you
will need to convert these into an array. For example:

```
const sandboxes = await (await Sandbox.list()).toArray();
```

If you want to iterate through the results and automatically let the SDK
query the pages when required:

```
const result = await Sandbox.list({ namePrefix: "ci-", limit: 20 });

# Iterate item by item.
for await (const sandbox of result) {
  console.log(sandbox.name, sandbox.status);
}

# Iterate page by page.
for await (const page of result.pages()) {
  console.log(`Got ${page.sandboxes.length} sandboxes, next=${page.pagination.next}`);
}
```

To support pagination, these previous 4 methods will support two new
optional parameters:

- `limit`: page size. Maximum is 100.
- `cursor`: starting point. The first page is fetched with this cursor;
from there the paginator follows pagination.next automatically.
…es (#173)

This PR adds support for L7 requests matcher and forward URLs in network
policies

⚠️ A follow-up PR will update to support the V2 API schema for network
policies, which will clean up the `toAPINetworkPolicy` /
`fromAPINetworkPolicy` logic

### Request matchers

For each `NetworkPolicyRule`, you can apply a matcher on:
- the request method 
- the request path
- the request query strings
- the request headers

Each matcher can target to either exactly match, start with, or regex
match, a provided value. For example, to inject an authorization header
on requests to Vercel's AI gateway but only on specific requests:

```
allow: {
  "ai-gateway.vercel.sh": [
    {
      match: {
        method: ["POST"],
        path: { startsWith: "/v1/" },
        headers: [
          { key: { exact: "x-api-key" }, value: { exact: "placeholder" } }
        ]
      },
      transform: [{
        headers: { authorization: "Bearer ..." }
      }]
    }
  ],
  "*": []
}
```

### Forward URLs

For each `NetworkPolicyRule`, you can define a `forwardURL` field that
serves as an HTTPS proxy to forward matching requests to. Since it's a
`NetworkPolicyRule`, you can also define matchers on it:

```
allow: {
  "ai-gateway.vercel.sh": [
    {
      match: {
        method: ["POST"],
        path: { startsWith: "/v1/" },
        headers: [
          { key: { exact: "x-api-key" }, value: { exact: "placeholder" } }
        ]
      },
      forwardURL: 'https://my-proxy.vercel.app'
    }
  ],
  "*": []
}
```
Follow-up to #173, update the
network policies requests to use the V2 schema, which supports matchers
and forward URLs

For now, the API responses are still under the V1 schema, because
changing it would be a breaking change. Only the requests are using the
V2 schema with this PR, which greatly simplifies `toAPINetworkPolicy`
# Conflicts:
#	.github/workflows/publish.yml
#	examples/filesystem-snapshots/CHANGELOG.md
#	examples/filesystem-snapshots/package.json
#	examples/workflow-code-runner/CHANGELOG.md
#	examples/workflow-code-runner/package.json
#	packages/sandbox/CHANGELOG.md
#	packages/sandbox/docs/index.md
#	packages/sandbox/package.json
#	packages/vercel-sandbox/CHANGELOG.md
#	packages/vercel-sandbox/package.json
#	packages/vercel-sandbox/src/api-client/api-client.test.ts
#	packages/vercel-sandbox/src/api-client/api-client.ts
#	packages/vercel-sandbox/src/command.serialize.test.ts
#	packages/vercel-sandbox/src/command.test.ts
#	packages/vercel-sandbox/src/command.ts
#	packages/vercel-sandbox/src/index.ts
#	packages/vercel-sandbox/src/sandbox.serialize.test.ts
#	packages/vercel-sandbox/src/sandbox.test.ts
#	packages/vercel-sandbox/src/sandbox.ts
#	packages/vercel-sandbox/src/snapshot.serialize.test.ts
#	packages/vercel-sandbox/src/snapshot.ts
#	packages/vercel-sandbox/src/utils/get-credentials.test.ts
#	packages/vercel-sandbox/src/utils/network-policy.test.ts
#	packages/vercel-sandbox/src/utils/network-policy.ts
#	packages/vercel-sandbox/src/utils/sandbox-snapshot.ts
#	packages/vercel-sandbox/src/version.ts
Co-authored-by: Gal Schlezinger <gal@spitfire.co.il>
Co-authored-by: Luke Phillips-Sheard <luke.phillips-sheard@vercel.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Luke PS <LukeSheard@users.noreply.github.com>
Co-authored-by: Malte Ubl <cramforce@users.noreply.github.com>
Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: Marc Codina <marc.codina@vercel.com>
Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Phil Z <pbzona@users.noreply.github.com>
Co-authored-by: Gregor Martynus <39992+gr2m@users.noreply.github.com>
Co-authored-by: Andy <76787794+AndyW22@users.noreply.github.com>
Co-authored-by: MelkeyDev <53410236+Melkeydev@users.noreply.github.com>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to named-sandboxes,
this PR will be updated.

⚠️⚠️⚠️⚠️⚠️⚠️

`named-sandboxes` is currently in **pre mode** so this branch has
prereleases rather than normal releases. If you want to exit
prereleases, run `changeset pre exit` on `named-sandboxes`.

⚠️⚠️⚠️⚠️⚠️⚠️

# Releases
## @vercel/sandbox@2.0.0-beta.18

### Minor Changes

- Add L7 request matchers and forward URLs support to network policy
rules. ([#173](#173))

## sandbox@3.0.0-beta.20

### Patch Changes

- Updated dependencies
\[[`0c62cab5ab16af355ca57d1a19ab1e6e70899060`](0c62cab)]:
    -   @vercel/sandbox@2.0.0-beta.18

## sandbox-filesystem-snapshots@0.0.17-beta.0

### Patch Changes

- Updated dependencies
\[[`0c62cab5ab16af355ca57d1a19ab1e6e70899060`](0c62cab)]:
    -   @vercel/sandbox@2.0.0-beta.18

## workflow-code-runner@0.1.6-beta.0

### Patch Changes

- Updated dependencies
\[[`0c62cab5ab16af355ca57d1a19ab1e6e70899060`](0c62cab)]:
    -   @vercel/sandbox@2.0.0-beta.18

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tom Lienard <tom.lienrd@gmail.com>
Add support for booting sandboxes with Node 26 on named sandboxes.
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to named-sandboxes,
this PR will be updated.

⚠️⚠️⚠️⚠️⚠️⚠️

`named-sandboxes` is currently in **pre mode** so this branch has
prereleases rather than normal releases. If you want to exit
prereleases, run `changeset pre exit` on `named-sandboxes`.

⚠️⚠️⚠️⚠️⚠️⚠️

# Releases
## sandbox@3.0.0-beta.21

### Patch Changes

- Add Node 26 support.
([#181](#181))

- Updated dependencies
\[[`b4126273497e08057bec448e965f3f157856254b`](b412627)]:
    -   @vercel/sandbox@2.0.0-beta.19

## @vercel/sandbox@2.0.0-beta.19

### Patch Changes

- Add Node 26 support.
([#181](#181))

## sandbox-filesystem-snapshots@0.0.17-beta.1

### Patch Changes

- Updated dependencies
\[[`b4126273497e08057bec448e965f3f157856254b`](b412627)]:
    -   @vercel/sandbox@2.0.0-beta.19

## workflow-code-runner@0.1.6-beta.1

### Patch Changes

- Updated dependencies
\[[`b4126273497e08057bec448e965f3f157856254b`](b412627)]:
    -   @vercel/sandbox@2.0.0-beta.19

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@LukeSheard LukeSheard closed this May 12, 2026
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sandbox-sdk Ready Ready Preview, Comment May 12, 2026 9:46am

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants