Commit Graph

1073 Commits

Author SHA1 Message Date
github-actions[bot] 2d2823c94a [skip ci] Release new versions 2026-08-07 14:15:10 +00:00
Mish Ushakov 88f41f3927 fix(python-sdk): port current JS stripAnsi regex to strip_ansi_escape_codes (#1545)
## Summary

The Python SDK's `strip_ansi_escape_codes` (used to clean template build
log messages) still used the old ansi-regex pattern, while the JS SDK's
`stripAnsi` was rewritten in #895. This ports the current JS regex to
Python so both SDKs clean logs identically: OSC sequences (hyperlinks,
window titles) are matched non-greedily up to the first string
terminator — including content spanning newlines — and CSI sequences are
stripped without requiring a terminator.

Following review feedback, both implementations now also strip the
remaining ECMA-48 string controls — DCS (Sixel, tmux passthrough), SOS,
PM, and APC — through their string terminator, so control payloads don't
leak into cleaned logs. This goes beyond upstream `chalk/ansi-regex`,
click, and Rich, none of which fully strip DCS payloads, and restores
what the old Python pattern handled.

Also mirrors the Python test suite into the JS SDK (which previously had
no `stripAnsi` tests) — 20 identical cases per side — and verified
byte-for-byte identical output between the two implementations on all of
them. Includes a patch changeset for `e2b` and `@e2b/python-sdk`.

## Example

Log messages that previously leaked OSC or DCS sequences into template
build output are now cleaned:

```python
from e2b.template.utils import strip_ansi_escape_codes

strip_ansi_escape_codes("\x1b]8;;https://e2b.dev\x07E2B\x1b]8;;\x07")  # "E2B"
strip_ansi_escape_codes("\x1b]0;title\nstill title\x07done")           # "done"
strip_ansi_escape_codes("\x1b[38:2::255:0:0mRED\x1b[0m")               # "RED"
strip_ansi_escape_codes("\x1bPq#0;2;0;0;0~~@@\x1b\\image")             # "image" (Sixel DCS)
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 15:04:40 -07:00
Mish Ushakov 86f7b8e2f8 fix(js-sdk): export the Git argument and status types (#1642)
Carries the change from #1635 (by @karpovantonme) into `main` as a
single squash commit — #1635 was retargeted at
`fix/export-git-argument-types`, merged there, and this PR promotes that
branch.

`Git.reset()`, `Git.restore()` and `Git.status()` are public, but the
types naming their arguments and results were not reachable from the
package entry point.

`src/index.ts` re-exported fifteen `Git*` types and omitted
`GitResetMode`, `GitResetOpts` and `GitRestoreOpts`. `GitStatusLabel`
was worse off — `src/sandbox/git/index.ts` re-exported `GitBranches`,
`GitConfigScope`, `GitFileStatus` and `GitStatus` from `./utils` but not
`GitStatusLabel`, so it was unreachable from anywhere in the package,
even though it is the type of `GitFileStatus.status`.

The practical effect: you could call the methods, but you could not name
what you pass them, so you could not write a typed wrapper.

```ts
// before — all four fail
import type {
  GitResetMode,
  GitResetOpts,
  GitRestoreOpts,
  GitStatusLabel,
} from 'e2b'

// the workaround people end up with
type ResetMode = Parameters<Git['reset']>[0] extends { mode?: infer M } ? M : never
```

```ts
// after
import { Sandbox } from 'e2b'
import type { GitResetMode, GitResetOpts, GitStatusLabel } from 'e2b'

async function hardResetTo(sbx: Sandbox, repo: string, target: string) {
  const mode: GitResetMode = 'hard'
  const opts: GitResetOpts = { mode, target, cwd: repo }
  return sbx.git.reset(opts)
}

function isBlocking(status: GitStatusLabel) {
  return status === 'conflict' || status === 'deleted'
}
```

## On SDK parity

Python already exports `GitResetMode` (`e2b.GitResetMode`), so this
brings JS up to it. The other three have no Python counterpart by
design: the sync and async implementations take keyword arguments rather
than option objects, so there is nothing shaped like
`GitResetOpts`/`GitRestoreOpts`, and `GitFileStatus.status` is typed as
a plain `str` there, so there is no `GitStatusLabel` either. Nothing to
mirror on the Python side.

## Notes

- Type-only re-exports, no runtime change. Changeset included (`e2b`:
patch).
- No test: a missing re-export is invisible to `tsc --noEmit` because
`packages/js-sdk/tsconfig.json` includes only `src`, so an `import type
… from '../src'` test passes either way. A declaration-reading test was
dropped from #1635 during review.

## Not touched

The same gap exists for a few non-Git types — `FilesystemListOpts`,
`WatchOpts`, `PtyCreateOpts` and `PtyConnectOpts` are exported from
their own modules but not from `src/index.ts`. Scope kept to the Git
surface, as in #1635.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Anton Karpov <30812217+karpovantonme@users.noreply.github.com>
Co-authored-by: Anton Karpov <karpovantonme@gmail.com>
2026-08-05 15:49:02 +02:00
github-actions[bot] 7a1fe4528c [skip ci] Release new versions 2026-08-03 19:45:46 +00:00
Joe Lombrozo 2821fb0b69 feat(sdk): route volume content to BYOC cluster domain (#1634)
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>
2026-08-03 10:24:15 -07:00
github-actions[bot] 9ef3f1dbbe [skip ci] Release new versions 2026-07-31 19:39:47 +00:00
Mish Ushakov 1ebe925ee0 fix(js-sdk): detect web platform objects by shape, not by class (#1618)
SDK-299

## Symptom

Two shapes of failure, one cause.

Every control-plane call crashing in an app that embeds the SDK next to
a server shim:

```
TypeError: Failed to parse URL from [object Request]
    at fetch (…/undici/index.js:157:10)
    at wrapped (…/e2b/src/undici.ts:126)
```

…and, quietly, uploads that arrive at the sandbox containing the eight
bytes `[object Blob]` instead of the file.

## Root cause

`value instanceof Blob` does not answer *"is this a Blob"*, it answers
*"was this minted by the `Blob` class this module happens to see"*. In a
Node process those are different questions: libraries replace the web
globals exactly the way they replace `globalThis.fetch` —
`@hono/node-server` installs its own `Request`, remix's
`installGlobals()` swaps `Request`/`Blob`/`File`, `web-streams-polyfill`
swaps `ReadableStream`, jsdom-style test environments bring their own
copies of all of them — and values also cross realms (`node:vm`,
`worker_threads`). `src/undici.ts` already late-binds the global `fetch`
for this reason; the brand checks never got the same treatment.

It is reachable with a **single** shim install, no exotic dependency
duplication:

1. `openapi-fetch` captures `Request: CustomRequest =
globalThis.Request` when the client is created (`dist/index.mjs:11`) and
mints every request from it,
2. `EnvdApiClient` is built once in the `Sandbox` constructor and stored
(`src/sandbox/index.ts:201`), so it outlives anything that swaps the
global afterwards,
3. from then on the SDK checks each request against a class that did not
mint it. Which copy "wins" the global is import-order dependent, so the
crash appears and disappears with unrelated dependency changes.

Verified locally, frame for frame: real `undici`/`undici8` throw
`TypeError: Failed to parse URL from [object Request]` for **any**
`Request` they did not mint — including Node's native one — so the
destructure in `toUndiciRequestInput` is load-bearing and a missed brand
check is fatal rather than merely slower.

## The whole family

Every brand check on the data path had the same defect, and each one
failed differently:

| Site | Misfires on | User-visible effect |
| --- | --- | --- |
| `undici.ts` `toUndiciRequestInput` | foreign `Request` | **every API
call throws** `Failed to parse URL from [object Request]` |
| `api/inflight.ts` `limitConcurrency` | foreign `Request` | abort
signal ignored while the request waits for a slot |
| `utils.ts` `toBlob` | foreign `Blob` | upload body is the text
`"[object Blob]"` |
| `utils.ts` `toBlob` | foreign `ReadableStream` | upload body is the
text `"[object ReadableStream]"` |
| `utils.ts` `toUploadBody` (gzip) | foreign `ReadableStream` |
`pipeThrough(new CompressionStream())` never settles — the upload hangs
|
| `utils.ts` `toUploadBody` | foreign `ReadableStream` | file buffered
into memory instead of streamed (OOM on large files) |
| `filesystem/index.ts` `hasStreamableData` | foreign `ReadableStream` |
same, plus the multipart path is chosen for a stream |
| `volume/index.ts` `readFile` | foreign `Blob`/`ArrayBuffer` |
**returns an empty file** |
| `undici.ts` `toUndiciRequestInput` (body) | foreign `Request`'s stream
body | body sent as the text `"[object ReadableStream]"` |

The `Blob`/stream rows are silent data corruption, confirmed against
real undici:

```js
await new Response(foreignBlob).text() // → "[object Blob]"
await new Response(foreignStream).text() // → "[object ReadableStream]"
```

## Fix

New internal `src/is.ts` asks what a value *is*: `instanceof` stays the
fast path, then it falls back to the members and `Symbol.toStringTag`
the platform guarantees (`isRequestLike`, `isBlobLike`,
`isReadableStreamLike`, `isArrayBufferLike`). Nothing is added to the
public surface.

Detection alone is not enough where the SDK hands data back to the
platform — the platform brand-checks too, and a detected-but-not-adopted
foreign stream would be stringified instead of buffered, i.e. worse than
before. So the conversions adopt what they detect:

- `toBlob` copies a foreign `Blob`'s bytes (`new Blob([await
data.arrayBuffer()], { type: data.type })`) and pumps a foreign stream
through a native one via its reader;
- the adoption itself is not class-dependent, which took two rounds to
get right (see *Adoption* below);
- `toUploadBody` returns `{ body, streamed }` instead of leaving
`filesystem`/`volume` to re-derive "did it stream?" with another brand
check on the result — only that function knows the decision it made.

### What used to break

```ts
import { serve } from '@hono/node-server' // installs its own globalThis.Request
import { Sandbox } from 'e2b'

const sandbox = await Sandbox.create()
await sandbox.files.write('/tmp/a.txt', 'hi') // TypeError: Failed to parse URL from [object Request]
```

```ts
import { ReadableStream } from 'web-streams-polyfill' // not the native class

// Used to upload the literal text "[object ReadableStream]"; with gzip it hung.
await sandbox.files.write('/tmp/big.bin', bigPolyfillStream, { gzip: true })
```

### Adoption

The platform accepts exactly two kinds of stream body: **its own
class**, and **any async iterable** (verified against `undici@8`/Node —
everything else is stringified). Async iterability is the half that
survives a replaced global, since a native stream stays async-iterable
even when `globalThis.ReadableStream` is a polyfill. Hence two helpers,
each named for the contract it satisfies:

- `toDispatchableStream` — for request bodies: passes through the
platform's own class *or* an async iterable, adopts the rest. Adopting
on `!(stream instanceof ReadableStream)` alone would have been
class-dependent in the same way as the bug: with a polyfilled global, a
perfectly good native stream fails the check and gets re-wrapped into a
polyfill instance the platform likes *less*.
- `toNativeStream` — for `pipeThrough(new CompressionStream(…))`, the
stricter consumer: it insists on its own class, so async iterability is
not enough there and every foreign stream is adopted.

Everything that isn't already a stream reaches the gzip path through
`toBlob`, whose result is always a native `Blob`, so the gzip path needs
no blob branch of its own. That in turn means nothing ever calls
`stream()` on a `Blob` the SDK didn't make, so `isBlobLike` only
requires `arrayBuffer` beyond the tag — one less requirement is one less
implementation whose upload would silently be the text `"[object
Blob]"`.

The same reasoning applies one level down: a `Request` from another
fetch implementation exposes *that* implementation's stream as its
`body`, so accepting the Request without adopting its body would only
move the stringification, not remove it.

## Tests

`tests/foreignPlatformObjects.ts` provides the fixtures: `ForeignBlob`
and `foreignReadableStream` are separate implementations rather than
subclasses (a subclass still passes `instanceof`, so it would prove
nothing), and `foreignRequestClasses()` returns two sibling subclasses
of the native `Request` — instances of one are fully functional Requests
that the other disowns, which is what the shims actually produce.

- `tests/is.test.ts` — the four predicates, including the negatives that
keep them honest (a `URL`, a string, an `IncomingMessage`-shaped `{ url,
method, headers }`).
- `tests/utils.test.ts` — content round-trips for
`toBlob`/`toUploadBody` across native/foreign `Blob`s and streams, plus
a gzip round-trip through `DecompressionStream` for all four input
kinds.
- `tests/undici.test.ts` — a disowned `Request` reaches undici
destructured as `(url, init)`; and, on Node, the same request goes
through the **real** undici `fetch` (via `MockAgent`, so no network) and
is matched on method, path and headers.
- `tests/api/inflight.test.ts` — an already-aborted disowned `Request`
rejects with `AbortError` without consuming a slot.

Each adoption rule has a test that fails without it: dropping the
async-iterable clause fails the "does not re-wrap a native stream when
the global class was replaced" case, using `toDispatchableStream` in the
gzip path fails the async-iterable gzip case, and dropping the body
adoption fails with `expected '[object ReadableStream]' to be 'hello'`.

Red/green: with `src/` reverted, the three Request tests fail (the
undici one with the production error, `ERR_INVALID_URL { input: '[object
Request]' }`) and the data-path tests fail with `expected '[object
Blob]' to be 'hello'` / `expected '[object ReadableStream]' to be
'hello'`.

Verification run: unit + `connectionConfig` (129), `tests/sandbox/files`
+ `tests/volume` against prod (92 passed, real streamed/gzipped
uploads), all four changed files green on Node, Bun, Deno and workerd,
plus `tsc`, `lint`, `prettier` and `build`.

## Out of scope

`network.rules instanceof Map` (`sandboxApi.ts`) and the `instanceof
Error` checks are left alone: nothing replaces `globalThis.Map`, and the
errors are ours. Python needs no counterpart — its REST stack takes
bytes/iterables and has no equivalent brand checks.

Supersedes #1610 (@himself65), which fixed the `Request` half of this
and diagnosed the crash; the reproduction there is what led to auditing
the rest.


🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-31 12:28:51 -07:00
Mish Ushakov 2df7651ee6 test(sdk): run firewall transform tests against an httpbin sidecar sandbox (#1631)
Follow-up to #1632, which added the template this depends on. Now
rebased onto `main`, so this is just the test change.

## Problem

The firewall transform tests asserted header injection by curling
`httpbin.e2b.team`, an externally hosted service the suite had to keep
alive.

## Fix

Starts a sidecar sandbox from the `httpbin` template instead: the rule
is keyed on the sidecar's `getHost(8080)` and the assertion reads the
injected header back from `/headers`, in the JS, sync Python, and async
Python suites. The sidecar's ready command has already passed by the
time `create` resolves, so the server is serving and no readiness
polling is needed. The template name lives in one fixture per SDK —
`httpbinTemplate` in `tests/template.ts` and the `httpbin_template`
fixture in `conftest.py`.

Also drops two comments merged in #1632 that claimed the tests spawn
`e2b/httpbin`. The bare alias is what resolves, same as `base` — the
team slug only appears in the display name.

⚠️ Do not merge before **Build and push prepared templates** has been
dispatched with `template: httpbin` — the tests resolve the template by
name and fail until it exists on the E2B team.

Verified against production: all three tests pass with the injected
header reflected by the sidecar, spawning the template by its bare alias
with a key that owns it — the same situation as CI.

SDK-304

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-30 23:37:14 +02:00
Mish Ushakov 4a2571d321 test(js-sdk): wait for workers.dev propagation in the CF deploy suite (#1592)
## Problem

The `cloudflare-deploy` CI job intermittently fails with `non-JSON
response (404): <!DOCTYPE html>...` ([example
run](https://github.com/e2b-dev/E2B/actions/runs/30009788154/job/89214641280)).
The truncated HTML boilerplate is easy to mistake for a Cloudflare
captcha/challenge page, but it's the standard Cloudflare **404** page:
each `wrangler deploy --temporary` lands on a brand-new account
subdomain (`e2b-js-sdk-smoke.<random>.workers.dev`), and Cloudflare
serves "nothing is here yet" until the route propagates to the edge. The
in-test retry window (initial attempt + 10 retries × 3s ≈ 35s) wasn't
always enough.

## Fix

- `setup.mts`: after the deploy, poll the worker URL until the worker
itself answers (405 to GET — the worker is POST-only), with a 240s
deadline. Tests only start once the route is live. Only the propagation
404 and thrown fetch errors (transient DNS/connect) keep the poll
waiting — any other status (403 challenge, 500 from a broken worker,
...) is a real failure and fails the setup immediately, with the error
page's `<title>` in the message.
- `run.test.ts`: retry only on the propagation 404 / `fetch failed`, so
other Cloudflare error pages propagate on the first attempt; include the
page `<title>` in the `non-JSON response` error, since the truncated
body is boilerplate shared by every Cloudflare error page.

Test-only change, no changeset.

## Verification

Ran `pnpm test:cf:deploy` against real Cloudflare (both revisions):

```
Deployed: https://e2b-js-sdk-smoke.quick-bike.workers.dev
Worker route not live yet (404), waiting...
Worker route not live yet (404), waiting...
Worker is live.

 Test Files  1 passed (1)
      Tests  1 passed (1)
```

The fresh subdomain served 404 for ~6s post-deploy — exactly the failure
mode from CI — then the suite passed on the first test attempt. `pnpm
run format`, `lint`, and `typecheck` pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 09:30:43 -07:00
Tomas Srnka 6733f36755 fix(sdk): align Python Fedora/Alpine image defaults with JS (#1625)
Python `from_fedora_image` defaulted to `fedora:42` (end-of-life) and
`from_alpine_image` to `alpine:3.22`, while JS already pinned
`fedora:44`/`alpine:3.24` — the same call produced a different base
image per SDK. Aligns Python; also fixes the JS type docs, which still
named the old defaults.

```python
Template().from_fedora_image()  # fedora:44 (was fedora:42)
Template().from_alpine_image()  # alpine:3.24 (was alpine:3.22)
```

Follow-up to #1612; both defaults are still unreleased.
2026-07-30 16:04:39 +00:00
Tomas Srnka 1504fbc843 SDK: fromFedoraImage/fromAlpineImage/fromArchImage helpers (#1612)
## What
Adds the missing non-Debian base-image convenience helpers to **both
SDKs**, mirroring the existing
`fromUbuntuImage`/`fromDebianImage`/`fromPythonImage`/`fromNodeImage`/`fromBunImage`:

- **JS/TS** (`packages/js-sdk`): `fromFedoraImage(variant?)`,
`fromAlpineImage(variant?)`, `fromArchImage(variant?)` + unit tests
- **Python** (`packages/python-sdk`): `from_fedora_image(variant)`,
`from_alpine_image(variant)`, `from_arch_image(variant)` + sync/async
unit tests

## Why
This is the **customer-facing half** of infra **#3381** (distro-aware
template provisioning). The engine now builds + boots
Ubuntu/Debian/Fedora/RHEL-family/Arch/Alpine on real KVM; before this PR
the SDK exposed distro helpers for the Debian family only, so
Fedora/Alpine/Arch were reachable only via the generic `fromImage()`.
These give them first-class parity.

## Verification (honest)
- **New helper unit tests pass locally** — JS `fromDistroImages.test.ts`
→ 6/6 green (`vitest`, no auth). Python `test_from_distro_images.py`
(sync + async) committed.
- **Full integration suite**: requires E2B API keys — fails locally with
`AuthenticationError` **identically on `main`** (215/187/29), i.e.
**zero regression** from this change; CI runs it with secrets.
- Lint scoped to the touched files.

## Not in this PR
The public **docs** still state *"only Debian-based images …
Alpine/RedHat not supported"* — but that text lives in
**`e2b-dev/docs`**, not this monorepo, so it's a **separate docs PR**
(being opened against `e2b-dev/docs`). Flagging so this + that land
together.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-30 12:17:02 +02:00
Mish Ushakov ee0ad25117 docs(sdk): rename team to project in snapshot docstrings (#1562)
Renames team → project terminology in the JS and Python SDK snapshot
docstrings: the `list_snapshots`/snapshot list `name` filter example now
reads `"my-project/my-snapshot"`, and `SnapshotInfo.names` is documented
as "including project slug and tag (e.g. project-slug/my-snapshot:v2)".

Documentation-only — no exported names, runtime behavior, or wire
protocol change; generated API clients and `spec/openapi.yml` are
intentionally untouched until the backend exposes project-named
endpoints. Includes a patch changeset for `e2b` and `@e2b/python-sdk`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 16:15:24 +02:00
github-actions[bot] cf8296cf89 [skip ci] Release new versions 2026-07-27 13:36:02 +00:00
Mish Ushakov 178e267ba2 fix(js-sdk): bump deprecated glob@^11 to ^13 (#1613)
Closes #1611.

`e2b` declared `"glob": "^11.1.0"`, and glob 11 is deprecated on npm, so
**every** `npm install` of any project that depends on `e2b` — directly
or transitively — printed a deprecation warning. Downstream packages
can't silence it themselves: npm `overrides` and `npm-shrinkwrap.json`
only apply to the top-level project being installed, not to a transitive
dependency's own range. It can only be fixed here.

Thanks @clayboby for the report and the verification work.

## Before / after

```console
$ npm install e2b@2.36.0        # before
npm warn deprecated glob@11.1.0: Old versions of glob are not supported, and contain
widely publicized security vulnerabilities, which have been fixed in the current version.
added 37 packages in 1s

$ npm install e2b               # after (this branch, packed locally)
added 26 packages in 1s
```

No API change — this is a dependency bump. The 37 → 26 package drop
comes from glob 13 moving its CLI (and
`jackspeak`/`@isaacs/cliui`/`string-width`/… ) out to a separate
`glob-bin` package.

## Why ^13 is safe

glob 12 and 13 only made **CLI-only** breaking changes, per [glob's
changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md):

- **v12** — "Remove the unsafe `--shell` option."
- **v13** — "Move the CLI program out to a separate package,
`glob-bin`."

The SDK's only use of glob is `getAllFilesInPath` in the template build
path (`src/template/utils.ts`, loaded via `dynamicImport('glob')`),
which touches the named async export `glob(pattern, opts)`, the options
`ignore` / `withFileTypes` / `dot` / `cwd`, and `Path#isDirectory()` /
`#fullpath()` / `#relative()`. All unchanged in 13.

glob 13.0.6's `engines` (`18 || 20 || >=22`) satisfy the SDK's
(`>=20.18.1 <21 || >=22`), and it's still dual CJS/ESM, so both build
outputs resolve it.

## Also in this PR: `"types": ["node"]` in the js-sdk tsconfig

glob 13 pulls `minipass@^7.1.3`, which removed the `/// <reference
types="node" />` that TypeScript 7's native `tsc` was (accidentally)
relying on to see Node globals — it doesn't auto-include
`node_modules/@types`. Without this, the bump fails `tsc --noEmit` with
~25 `TS2591 Cannot find name 'process'/'Buffer'` errors. Requesting
`node` explicitly is the right fix and makes the typecheck independent
of a transitive dependency's d.ts.

## Verification

- `tsc --noEmit` clean for js-sdk and cli; `pnpm run lint` / `format`
clean; `tsdown` build clean and `glob` still emitted as an external
`dynamicImport("glob")`, not inlined.
- `getAllFilesInPath` unit suite (17 tests: ignore patterns,
dotfiles/dotdirs, recursive dirs, deterministic sort, `.` pattern) green
against the real glob 13.0.6 on Node, **Bun 1.3.14, and Deno 2.8.1**.
- Full `unit` + `connectionConfig` projects: 401 passed / 30 skipped
against prod.
- Full `template` project: 133 passed / 3 skipped, including real
end-to-end template builds that exercise `COPY` (the glob path).
- Packed the tarball and installed it into a scratch project to confirm
the warning is actually gone (output above), plus CJS `require('e2b')`
and ESM `import 'e2b'` both load.

## Not fixed here

`@e2b/cli` installs still warn, via `@npmcli/package-json@5.2.1 →
glob@10.5.0`. Clearing that needs `@npmcli/package-json@7`, whose
`engines` (`^20.17.0 || >=22.9.0`) are narrower than the CLI's own
(`>=20.18.1 <21 || >=22`, so Node 22.0–22.8 would drop out) — separate
change, separate decision.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 14:39:35 +02:00
Mish Ushakov b5119539ca fix(js-sdk): share lazy fetcher loading and drop the new Function import trick (#1607)
Extracts the duplicated api/envd fetcher-loading logic into shared
`createRuntimeFetch` + `buildDispatchedFetch` helpers in `undici.ts`,
fixing two behaviors along the way: a failed fetcher build is no longer
cached forever (the next request retries, with a guarded
compare-and-clear so a stale awaiter can't clobber a newer in-flight
build — covered by a regression test reproducing the microtask
interleaving), and the no-undici fallback now late-binds
`globalThis.fetch` so fetch replacements installed after the first
request (msw, instrumentation) are picked up.

`loadUndici` now uses the shared `dynamicImport` helper, whose import is
kept opaque to downstream bundlers via `webpackIgnore`/`@vite-ignore`
annotations instead of the `new Function('return import(...)')` trick —
so environments that disallow code generation from strings (CSP,
`--disallow-code-generation-from-strings`) now load undici normally
instead of silently degrading to the global fetch. The now-internal
`toUndiciRequestInput`/`UndiciRequestInit` are no longer exported. No
user-facing API changes; verified with the unit suites (lint/typecheck
clean) plus real API integration tests through the new dispatcher path.

### Test-suite fallout from the import fix

Dropping the `new Function` trick exposed a hidden test dependency: that
trick throws under vitest's vm evaluation, so every vitest run had
silently fallen back to the msw-patchable global fetch. With module
loading un-broken, the Node test runs dispatched through real undici,
bypassing msw's `globalThis.fetch` patch — mocked requests escaped to
the real API (real 404s in the tags suite, 3-minute hangs in the
abortSignal suites waiting for msw's `request:start`, and 28 real
template builds per run from the stacktrace suite).

Fixed centrally: `tests/globalFetchFallback.setup.ts`, registered via
`setupFiles` for the unit and template projects, mocks
`buildDispatchedFetch` to run the SDK's real undici-unavailable fallback
(late-bound `globalThis.fetch`), so msw suites need no per-file mock and
future msw suites are covered automatically. Suites that inject their
own `loadUndici` (the api/envd transport tests) keep it, so the
dispatcher wiring itself stays covered. Verified under Node, Bun, and
Cloudflare workerd.

Closes SDK-290

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 12:37:14 -07:00
github-actions[bot] 59c6996a1e [skip ci] Release new versions 2026-07-24 14:45:57 +00:00
Mish Ushakov 4fcf7cb150 feat: sync API specs from infra and belt with Copybara (#1564)
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>
2026-07-24 16:37:02 +02:00
Mish Ushakov ada1744cf1 test(js-sdk): run the template test suite on Bun (#1600)
## Description

Adds `--project template` to `test:bun` so the Bun CI leg runs the
template suite, matching the Deno leg (#1595).

No code changes are needed: the template suite previously failed under
Bun because Bun's JavaScriptCore elides tail-call frames and the
fixed-depth stack walk attributed build errors one frame past the user's
call site (the workaround attempt in #1596 was closed in favor of
#1599). With #1599's boundary-based frame selection (now merged), the
suite passes under Bun as-is.

The CI workflow already passes `E2B_API_KEY`/`E2B_DOMAIN` to the Bun
leg, and the matrix comment (updated in #1595) already covers Bun
re-running API-backed suites, so `package.json` is the only change.

## Testing

Full `test:bun` (unit + connectionConfig + template) green locally on
Bun 1.3.14 against the real API: 530 passed, 35 skipped, 0 failed —
including all 34 stack-trace/caller-directory tests that pin exact user
call-site line/columns, the frames Bun used to elide.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 12:51:24 +00:00
Mish Ushakov 1ae3f92090 feat(js-sdk): run the full unit test suite in Cloudflare workerd (#1593)
## What

Promotes `test:cf` from a single dist smoke test to the **full unit +
connectionConfig suite running inside Cloudflare's workerd**
(`@cloudflare/vitest-pool-workers`) — the same coverage `test:bun` and
`test:deno` get. Locally: **74 files / 393 tests green** against prod
sandboxes. The real-deploy suite (`test:cf:deploy`) is unchanged and
keeps covering the built bundle on actual Cloudflare infrastructure (the
pool can't reproduce bundling bugs like #1579).

## SDK fixes the suite surfaced

1. **Dropped-connection mapping for Workers** (`src/envd/rpc.ts`):
workerd surfaces a sandbox connection drop as `Network connection lost`,
which fell through to a cryptic `SandboxError`. It's now matched like
the Node/Bun/Deno variants, so killing a sandbox mid-request surfaces as
the health-checked `TimeoutError`:

   ```ts
const cmd = await sandbox.commands.run('sleep 60', { background: true })
   await sandbox.kill()
await cmd.wait() // now rejects with TimeoutError('…sandbox was killed
or reached its end of life…') on Workers too
   ```

2. **Double connection release on stream cancel**
(`src/connectionConfig.ts`): `wrapStreamWithConnectionCleanup` claimed
its `release` was idempotent but had no guard — cancelling a streamed
download while a read was in flight ran `cleanup()` twice (both the
`cancel` callback and the pending `pull` resolving `done` fire).
workerd's stream scheduling hits this deterministically; the pooled
connection was double-released.

Both are runtime-behavior fixes specific to the JS fetch/streams stack —
no Python SDK equivalent applies.

## Test adjustments

- **boot_id reads** in the two "filesystem-only pause" tests now use
`commands.run('cat …')` instead of `files.read`: envd's non-gzip
download path serves procfs files as an empty 200 (filed as
e2b-dev/infra#3363 — Go `ServeContent` sizes them by stat, which is 0).
Only clients that don't negotiate gzip (workerd's fetch) observe it; the
command path sidesteps the bug while keeping the reboot assertion on all
runtimes.
- **runtime.test.ts** Node-host detection scenarios skip under workerd
via the existing host guard (same treatment as Bun/Deno).
- **Pool config filters expected unhandled-rejection shapes** via
vitest's `onUnhandledError` (not the blanket
`dangerouslyIgnoreUnhandledErrors`): workerd reports a rejection as
unhandled unless a handler attaches within the same microtask drain —
even inline `await expect(op()).rejects` trips it — and vitest never
processes the `rejectionhandled` retraction on any runtime, so the
suite's deliberate rejections false-positive ~60× per run. A diagnostic
pairing `unhandledrejection` with `rejectionhandled` confirmed all of
them are handled-late false positives (zero genuine leaks). The filter
drops only the shapes the tests provoke (SDK error classes,
`ConnectError`, `AbortError`, workerd's `Network connection lost.`, one
test stub); unknown rejection shapes and uncaught exceptions still fail
the run — verified with a planted never-handled `TypeError` (exit 1).

## CI

Rebased onto #1588's per-runtime matrix: the `cloudflare` leg (already
ubuntu-only there) now runs the full suite; no extra jobs added. The
stale `tests/integration` exclude was dropped after #1591 removed that
suite.

## Notes

- Suite config needs `nodejs_compat_populate_process_env` +
`E2B_API_KEY`/`E2B_DOMAIN` miniflare bindings so the SDK and tests read
env like on Node.
- The deleted `tests/runtimes/cloudflare/run.test.ts` (dist smoke) is
fully subsumed: lifecycle coverage by the suite, bundle coverage by
`test:cf:deploy` + `tests/bundle/edgeCompat.test.ts`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:42:51 +02:00
Mish Ushakov 3f46d56026 fix(sdk): select stack-trace frames by SDK boundary instead of fixed depth (#1599)
## Description

Template build stack traces were captured by walking a fixed number of
frames (`STACK_TRACE_DEPTH` plus `±1` arithmetic at ~15 call sites),
which broke whenever the frame count between `new Error()` and user code
shifted — TS class-field initializer frames (#1539) and Bun's tail-call
frame elision were both this bug. This PR makes two related changes:

1. **Boundary-based frame selection.** The caller's frame is now the
first one whose file lies outside the SDK package, making extra
transpiler frames and elided delegating frames irrelevant. In the JS
SDK, frame parsing is delegated to `error-stack-parser-es` (ESM-only, so
it's a devDependency inlined into both dist formats via tsdown
`noExternal` — the engines range includes Node versions without
`require(esm)`); the Python SDK equivalently walks `f_back` until
`co_filename` leaves the `e2b` package root, in the shared builder used
by both sync and async. If no user frame is identifiable (e.g. the SDK
is bundled into the caller's own file), capture degrades to no trace
rather than a wrong frame.
2. **Dead machinery removed.** Because boundary capture resolves through
SDK-internal delegation (`remove()` → `runCmd()`, `fromDockerfile()` →
parser) to the user's call site on its own, the suppress/override
collection machinery (`runInNewStackTraceContext`,
`runInStackTraceOverrideContext`, the enabled/override flags, and their
Python equivalents) became redundant and is removed — superseding the
approach in #1596.

Error `.stack` synthesis (keeping the `Name: message` header and the
throw site on `cause`) was prototyped here and backed out — it will come
as a follow-up PR.

## Usage

No API changes — build errors now point at the user's call site
regardless of runtime or transpiler:

```ts
const template = Template()
  .fromBaseImage()
  .runCmd('./does-not-exist') // ← build failures point exactly here

await Template.build(template, 'my-template')
```

## Testing

- JS: `unit` + `template` vitest projects green against the real API
(incl. 27 per-method stacktrace tests pinning exact call-site
line/columns, `bunInstall` now covered); edge-compat bundle test and CLI
build verified; built CJS/ESM dists smoke-tested with
`require()`/`import()`.
- Python: all 184 template tests green (shared + sync + async, incl.
both `test_stacktrace.py` suites, `bun_install` now covered); `ruff` and
`ty` clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:42:40 +02:00
Mish Ushakov 5e141a765f fix(js-sdk): use commands.run in Sandbox.getHost() example (#1531) (#1550)
The JSDoc `@example` on the public `Sandbox.getHost()` method calls
`sandbox.commands.exec(...)`, but the `Commands` class has no `exec`
method. It
exposes `run`. Copy-pasting the documented snippet therefore throws:

```
TypeError: sandbox.commands.exec is not a function
```

### Where

`packages/js-sdk/src/sandbox/index.ts`, in the `getHost()` doc comment:

```ts
/**
 * ...
 * @example
 * ```ts
 * const sandbox = await Sandbox.create()
 * // Start an HTTP server
 * await sandbox.commands.exec('python3 -m http.server 3000')  // <- no such method
 * // Get the hostname of the HTTP server
 * const serverURL = sandbox.getHost(3000)
 * ```
 */
```

The `Commands` class (`packages/js-sdk/src/sandbox/commands/index.ts`)
exposes
`list`, `sendStdin`, `closeStdin`, `kill`, `connect`, and `run` (four
`run`
overloads), plus a private `start`. There is no `exec`. The correct
method here
is `run`, which is what every other example already uses, including the
sibling
`@example` in this same file (the `commands.run(...)` snippet a few
methods up)
and both Python SDK mirrors (`get_host` in `sandbox_sync/main.py` and
`sandbox_async/main.py` already use `commands.run`).

### Fix

One token, `exec` -> `run`:

```ts
- await sandbox.commands.exec('python3 -m http.server 3000')
+ await sandbox.commands.run('python3 -m http.server 3000')
```

Documentation only. No behavior or type change.

### Parity with the Python SDK

The repo guidelines ask that SDK changes be mirrored across the JS and
Python
SDKs. Here the Python `get_host` examples already use `commands.run`
correctly,
so this defect exists only in the JS SDK doc comment and no Python
change is
needed to reach parity.

### Tests

This is a JSDoc `@example` correction with no runtime code path to
exercise, so
it adds no test, matching the repo's existing precedent for
documentation-only
fixes (e.g. `.changeset/sandbox-list-docstring.md`, and merged doc-fix
PRs such
as #1511 / #1500 / #1260, none of which added a regression test).
Correctness is
that the example now names the real public API: after the change,
`commands.exec`
no longer appears anywhere in the SDK source, and `commands.run` matches
the
`Commands` class and the sibling examples.

Offline gates run locally (Node 20, pnpm 9.15.5):

```
pnpm --filter e2b run lint        # oxlint, clean
pnpm --filter e2b run typecheck   # tsc --noEmit, clean
pnpm --filter e2b run build       # tsc + tsup, ESM/CJS/DTS built
prettier --check src/sandbox/index.ts  # clean
```

A changeset (`e2b`, patch) is included.

---

## Linked issues

- None. There is no existing GitHub issue for this; it is a self-evident
public
doc-example defect (the documented snippet throws at runtime). Not
filing a
  separate issue for a one-token doc fix.

## Pre-flight checklist (repo AGENTS.md / CLAUDE.md gates)

- [x] `pnpm run format` - `prettier --check` clean on the changed file
- [x] `pnpm run lint` - oxlint clean (exit 0)
- [x] `pnpm run typecheck` - tsc --noEmit clean (exit 0)
- [x] `pnpm run build` - tsc + tsup clean
- [x] Changeset generated - `.changeset/fix-gethost-example-command.md`
(`e2b`: patch)
- [x] Conventional Commit message (`fix(js-sdk): ...`, reuses `js-sdk`
scope)
- [ ] Test added - not applicable (doc-only `@example`; see Tests
section for precedent)
- [ ] DCO / CLA - no sign-off required by this repo; CLA is signed via
`@cla-bot`
      on the PR after opening (as on prior PRs #1518 / #1519 / #1507)

Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
Co-authored-by: Anas Khan <anxkhn28@gmail.com>
2026-07-24 05:37:19 -07:00
Mish Ushakov 9ee4414e6d feat(js-sdk): run the template test suite on Deno (#1595)
## What

Extends the Deno vitest run (#1585) with the `template` project and
fixes the real runtime bug the suite surfaced. Split out of #1594 (Bun
counterpart: #1596).

```jsonc
// packages/js-sdk/package.json
"test:deno": "deno run -A npm:vitest run --project unit --project connectionConfig --project template",
```

## Bug — Deno: template uploads used chunked transfer encoding

Deno's native `fetch` ignores an explicit `Content-Length` header on
stream bodies and falls back to `Transfer-Encoding: chunked` — exactly
the failure #1243 fixed for Node, since S3-compatible presigned PUT URLs
reject chunked uploads with 501.

`uploadFile` now streams the spooled archive through **undici's
`fetch`** (via the existing `loadUndici()` helper — undici 8 where it
imports, undici 7 on Bun, global `fetch` where undici isn't resolvable,
e.g. bundled apps), which honors the `Content-Length` header on stream
bodies on every runtime. One upload path, no runtime sniffing.

Approaches rejected along the way, all verified empirically with 1GB
uploads + RSS sampling:

- **File-backed `Blob` body (`fs.openAsBlob`)** — lazy on Node/Bun, but
Deno's shim reads the whole file into memory eagerly
(denoland/deno#32316), and Bun infers an unstrippable MIME type from the
extension whose `Content-Type` breaks presigned signatures (403 against
production storage).
- **`node:http(s)` on Deno** — works (and is memory-bounded), but can't
be unified: Bun's `node:http` ignores abort signals, and it's a second
code path.

Known caveat: Deno's `Readable.toWeb` shim has no backpressure, so the
archive is buffered in memory during upload on Deno (Node and Bun stream
in lockstep with the socket). Filed upstream as denoland/deno#36275 —
accepted as Deno's to fix rather than worked around here.

As part of this, `tarFileStream` became `spoolTarArchive`, returning `{
path, size, cleanup }` with caller-owned cleanup instead of a
self-deleting read stream. `tests/template/uploadFile.test.ts` also
asserts no `Content-Type` header is sent.

## Python SDK parity

Intentionally none: `upload_file` already sends a sized file body via
httpx.

## Testing

- Real template builds (`tests/template/build.test.ts`, against prod S3
presigned URLs) green under **Node, Deno, and Bun**
- `uploadFile` + `spoolTarArchive` suites green under Node, Deno, and
Bun
- `tests/template/abortSignal.test.ts` green under Deno
- `pnpm build`, `lint`, `typecheck`, `prettier --check` clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 18:11:24 +00:00
Mish Ushakov 5417dd4f9f fix(deps): resolve all open Dependabot alerts (#1598)
## Summary

Fixes all 8 open [Dependabot
alerts](https://github.com/e2b-dev/E2B/security/dependabot), all in
`pnpm-lock.yaml`:

| Package | Severity | Alerts | Before | After | How |
|---|---|---|---|---|---|
| `@vitest/browser` | critical | #328 | 4.1.8 | 4.1.10 | updated the
vitest family in js-sdk and cli devDeps (4.1.10 peer-requires
`vitest@4.1.10` exactly) |
| `tar` | critical/high/medium ×4 | #324–#327 | 7.5.16 | 7.5.21 | bumped
the js-sdk runtime dep floor to `^7.5.19` + repo-wide override |
| `sharp` | high | #329 | 0.34.5 | 0.35.3 | new override (pinned exactly
by miniflare, dev-only) |
| `shell-quote` | high | #323 | 1.8.4 | 1.10.0 | widened existing
override (dev-only, via npm-run-all) |
| `brace-expansion` | high | #322 | 2.1.0 | 2.1.2 | widened existing
override |

The only runtime-dependency change is `tar` in the js-sdk (used for
template build contexts), so a patch changeset for `e2b` is included.
The CLI bundles the SDK and its dependencies into `dist/index.js`, so
the published CLI also ships the vulnerable `tar` — a patch changeset
for `@e2b/cli` is included to rebundle it. Everything else is dev
tooling or lockfile-only.

## Verification

- `pnpm run lint` and `pnpm run typecheck` pass (the 7 python-sdk ty
diagnostics pre-exist on main)
- js-sdk: unit + connectionConfig (393 passed) and template projects
(132 passed, exercises the new `tar` end-to-end against the real API) on
vitest 4.1.10; `pnpm run build` clean
- js-sdk `test:cf` passes — miniflare/workerd boots with sharp 0.35.3
- cli: full suite green (103 passed) on vitest 4.1.10

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 18:56:01 +02:00
Mish Ushakov 67bf112efc test(js-sdk): rename deprecated test.scoped() to test.override() (#1597)
vitest 4.1 deprecates `test.scoped()` in favor of `test.override()`,
emitting 15 warnings during test collection in CI. This renames all
`sandboxTest.scoped()` fixture overrides to `sandboxTest.override()`
across the six affected test files (network, snapshot, internetAccess,
secure, files/signing, commands/envVars). It's a pure rename — the
vitest 4.1.8 types confirm an identical signature — and `vitest list` on
all six files now collects with zero deprecation warnings. Test-only
change, so no changeset.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:13:25 +00:00
Mish Ushakov e00503b090 fix(ci): recover release 30006966441 and retry lockfile update with backoff (#1589)
## What happened

Release run
[30006966441](https://github.com/e2b-dev/E2B/actions/runs/30006966441)
successfully published **e2b@2.35.3** and **@e2b/cli@2.15.0** to npm and
pushed both tags, but then failed on the **Update lock file** step:
`pnpm i` ran ~6 seconds after `npm publish` and the registry had not
propagated the new version yet (`ERR_PNPM_NO_MATCHING_VERSION: No
matching version found for e2b@^2.35.3 — the latest release of e2b is
"2.35.2"`). Because that step failed, the **Commit new versions** step
was skipped, leaving main with stale versions and unconsumed changesets.

Auditing the rest of the publish path for similar races also turned up a
long-dead step: the `@e2b/sdk` alias republish.

## Changes

**Commit 1 — replay the missing release commit.** Reproduces exactly
what the bot would have committed: `pnpm run version` (consumes the
three changesets, bumps js-sdk 2.35.2 → 2.35.3 and cli 2.14.0 → 2.15.0)
followed by `pnpm i --no-link --no-frozen-lockfile` (now succeeds — the
registry has long since propagated). The only commit that landed on main
after the release was dispatched
([e334c87](https://github.com/e2b-dev/E2B/commit/e334c87f8fc60be56cc5970d6f6399331242bace))
touches only `.github/`, so per the workflow's own safety rule the
version bump is safe to apply on top: the published artifacts match the
source.

**Commit 2 — prevent recurrence.** The `Update lock file` step in
`publish_packages.yml` now retries with exponential backoff
(10/20/40/80/160s, up to ~5 min total) before failing, since the npm
registry is eventually consistent and this race will recur on any
release where propagation takes more than a few seconds.

**Commit 3 — remove the dead `@e2b/sdk` alias republish.**
`packages/js-sdk/scripts/post-publish.sh` republished each release under
the deprecated `@e2b/sdk` name and immediately re-deprecated it. It has
silently failed on every release since 2.5.0 (2025-10-28): the CI npm
token lacks publish rights to `@e2b/sdk` (`E404` on `PUT
https://registry.npmjs.org/@e2b%2fsdk`, npm's masking of 403) and the
`|| true` swallowed the error — visible in this run's log right before
the lockfile failure. All published `@e2b/sdk` versions already carry
the "renamed to e2b" deprecation notice, which is the coherent end
state; resuming alias publishes would only reward not migrating. The
script and its `postPublish` hook are deleted (the root `pnpm run -r
postPublish` stays — python-sdk still uses its hook for PyPI). No
changeset: nothing in the published artifact's runtime changes, and the
alias hasn't published in 9 months so user-visible behavior is
unchanged.

## Notes

- Please merge before the next release: until then main still claims
2.35.2/2.14.0, and a future `changeset version` run would compute wrong
bumps from the stale base.
- The version-bump commit intentionally consumes the existing three
changesets.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 07:34:25 -07:00
Mish Ushakov aa3c2593b9 test(js-sdk): remove unused integration test suite (#1591)
## Summary

Removes `packages/js-sdk/tests/integration/` — the suite was never wired
into CI: no workflow references `test:integration` or sets the
`E2B_INTEGRATION_TEST` env var that gated every test. The tests were
also stale, referencing hardcoded template IDs (`en716jw99aj63v1k8ugh`,
`integration-test-v1`) that likely no longer exist and passing
`timeoutMs: 120` (120 ms). Also removes the `test:integration` script,
the `integration` vitest project, and the unused `isIntegrationTest`
helper from `tests/setup.ts`. Test-only change, no changeset needed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:02:57 +00:00
Mish Ushakov e29d406887 feat(js-sdk): run the vitest unit suite on Deno (#1585)
## Description

`pnpm test:deno` now runs the full vitest suite — the `unit` and
`connectionConfig` projects, 421
sandbox/files/commands/pty/git/api/config tests — under the Deno runtime
via `deno run -A npm:vitest run --project unit --project
connectionConfig`, replacing the previous single dist-based smoke test
(superseded — the suite covers the SDK under Deno far more thoroughly).
The CI step runs on ubuntu only and covers the same projects as the Bun
suite step from #1584, and the Deno pin is bumped from 1.46.3 to 2.8.1
(`setup-deno@v2`) since vitest needs Deno 2's Node compat.

Also drops the `edge` vitest project: `tests/runtimes/edge/` no longer
exists, so it matched zero files.

Rebased on main after #1584: the off-Node fetch-caching fix originally
in this PR was superseded by #1584's late-binding fix, which also makes
the whole suite (including the per-proxy cache tests) pass under Deno
with no test changes — so this PR is pure test/CI wiring.

Verified locally on Deno 2.8.1: unit project green (349 passed, 0
failed, 29 skipped — same skips as Node), connectionConfig project green
(43 passed), and Node suite green.

## Usage

```bash
cd packages/js-sdk
pnpm test:deno
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:22:00 +02:00
Mish Ushakov d417e9c4e6 test(js-sdk): Cloudflare Workers smoke tests (workerd pool + real deploy) (#1586)
Adds two Cloudflare Workers smoke suites for the JS SDK, both exercising
the built `dist/index.mjs`: `pnpm test:cf` runs the sandbox lifecycle
inside workerd via `@cloudflare/vitest-pool-workers`, and `pnpm
test:cf:deploy` deploys a worker to an ephemeral Cloudflare preview
account (`wrangler deploy --temporary` in the suite's global setup — no
Cloudflare credentials needed) and asserts the same lifecycle against
the live `workers.dev` URL, deleting the worker in teardown. The pool
suite immediately caught a runtime-detection bug: Node-compat shims
populate `process.release.name` inside Workers, so `getRuntime()`
misdetected Workers as Node and loaded `undici`; explicit runtime
markers now take precedence over the generic Node check (unit-tested,
changeset included). Both suites run in CI after the build step,
alongside the Bun and Deno suites (deploy suite on ubuntu only).

> [!IMPORTANT]
> Merge #1583 first: the deploy suite reproduces the exact #1579 startup
crash (Cloudflare rejects the upload with validation error 10021,
`createRequire` receiving undefined `import.meta.url`) and stays red
until that fix lands. Verified green end-to-end with #1583 applied.

Usage:

```bash
cd packages/js-sdk && pnpm build

# sandbox lifecycle inside local workerd (vitest-pool-workers)
pnpm test:cf

# deploy to a temporary Cloudflare preview account, test the live worker, delete it
E2B_API_KEY=... pnpm test:cf:deploy
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 13:57:18 +02:00
Mish Ushakov a406f78658 feat(js-sdk): run the full test suite under Bun (#1584)
## What

Runs the JS SDK's full vitest suite (the `unit` and `connectionConfig`
projects — 419 tests) under the Bun runtime, replacing the previous
single `bun:test` smoke test (superseded — the suite covers the SDK
under Bun far more thoroughly).

- `pnpm test:bun` → `bunx --bun vitest run --project unit --project
connectionConfig`
- CI step in `js_sdk_tests.yml` (ubuntu only for now); the old smoke
test and its Windows Bun install are removed

## SDK fixes surfaced by running the suite under Bun

1. **Late-bind `globalThis.fetch` on non-Node runtimes**
(`src/api/http2.ts`, `src/envd/http2.ts`). The factories previously
returned the bare global `fetch` reference, so:
   - every per-proxy cache entry was the identical function, and
- a `fetch` swapped in *after* client creation (msw, instrumentation,
test stubs) was either ignored or — worse — a temporary stub was
captured permanently in the module-level fetcher cache.

   They now return a closure that reads `globalThis.fetch` at call time.

2. **Pin abort reasons to their `AbortController`**
(`src/connectionConfig.ts`). Bun (observed on 1.3.14) holds
`AbortSignal.reason` weakly: a timeout `DOMException` constructed inside
a `setTimeout` callback gets garbage-collected, so consumers saw
`signal.reason === undefined` instead of a `TimeoutError`. Reasons are
now also stored on the controller, keeping them alive, and a losing
(post-abort) call never overwrites the pin. No behavior change on other
runtimes.

   ```ts
// Before (on Bun): sandbox operations that timed out aborted with
reason undefined
// After: they abort with DOMException('Request handshake timed out
after 30000ms', 'TimeoutError')
   const sbx = await Sandbox.create({ requestTimeoutMs: 30_000 })
   ```

## Test changes

- `tests/envd/http2.test.ts`: the "uses global fetch outside Node" test
now asserts late-binding behavior (a fetch stubbed after fetcher
creation is picked up) instead of reference identity.
- `tests/volume/volume.test.ts`: the msw-mocked `format: 'stream'` read
is split into its own test and skipped on Bun — reading `response.body`
of an msw-intercepted fetch via a reader yields an immediately-done
stream there (msw/Bun incompatibility; `.text()`/`.blob()` work). Real
network streams on Bun work and are covered by the sandbox `files.read`
tests that now run under Bun.

## Verification

Locally on Bun 1.3.14 (macOS arm64) and Node 22:

- `pnpm test:bun`: 73 files passed, 389 tests passed / 30 skipped, 0
failed
- `npx vitest run --project unit --project connectionConfig` (Node): 389
passed / 29 skipped, 0 failed
- browser project (chromium via playwright): passed
- `pnpm run format` / `lint` / `typecheck`: clean

Python SDK parity: not applicable — the changes are JS-runtime-specific
(Bun/global-fetch handling).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 11:33:09 +00:00
github-actions[bot] ab5f7666c9 [skip ci] Release new versions 2026-07-23 11:03:38 +00:00
Mish Ushakov f10989813c fix(js-sdk): drop bare require calls that crash edge runtimes at import (#1583)
Fixes #1579. Bare `require` references in the SDK's ESM source made
tsdown emit an eager `createRequire(import.meta.url)` shim at module
scope in `dist/index.mjs`, which throws in Cloudflare Workers (workerd)
where `import.meta.url` is undefined in bundled code — so `import 'e2b'`
crashed before any API call.

`sha256` now uses WebCrypto directly (the `node:crypto` fallback was
dead code, since package engines require Node ≥ 20.18.1 and
`globalThis.crypto` exists on all supported runtimes), and
`getCallerDirectory` loads `fileURLToPath` via a static top-level
`import url from 'node:url'`, matching the existing sibling
`node:fs`/`node:os`/`node:path` imports in the same file.
`dynamicRequire` is removed entirely (no remaining callers), and a new
bundle test (`tests/bundle/edgeCompat.test.ts`) fails the suite if a
`require` shim ever reappears in `dist/index.mjs` — it skips locally
when `dist/` hasn't been built and throws in CI, where the workflow
always builds first.

Verified against the issue's repro in real workerd via wrangler:
`e2b@2.35.1` reproduces the crash, while this build imports cleanly and
runs a full sandbox lifecycle from inside a Worker. Also verified:
chromium browser test, Bun runtime test, signing/secure tests (WebCrypto
signatures accepted end-to-end), and the full template suite (134 tests)
against live infra.

### Usage

No API changes — importing the SDK in a Cloudflare Worker (with
`nodejs_compat`) now works again:

```ts
import { Sandbox } from 'e2b'

export default {
  async fetch(request: Request, env: Env) {
    const sandbox = await Sandbox.create({ apiKey: env.E2B_API_KEY })
    const result = await sandbox.commands.run('echo hello from workerd')
    await sandbox.kill()
    return Response.json({ stdout: result.stdout })
  },
}
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 10:42:58 +00:00
github-actions[bot] 43db96a0ef [skip ci] Release new versions 2026-07-22 18:40:32 +00:00
Matt Brockman e5a4bd655d Use undici8.8 when on node >= 22.19 (#1575) 2026-07-22 10:36:04 -07:00
github-actions[bot] 50de0af442 [skip ci] Release new versions 2026-07-17 09:59:36 +00:00
Mish Ushakov 95e4dc2832 feat(sdk): add sandbox fork to JS and Python SDKs (#1554)
## 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>
2026-07-17 09:50:34 +00:00
github-actions[bot] 8c87016a57 [skip ci] Release new versions 2026-07-16 09:24:56 +00:00
Mish Ushakov 2c77fc00bb feat(sdk): add name filter to snapshot list (#1523)
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>
2026-07-16 11:06:37 +02:00
github-actions[bot] 78a91ab72f [skip ci] Release new versions 2026-07-15 09:27:07 +00:00
Mish Ushakov 64e9bc02b6 fix(js-sdk): unpin useDefineForClassFields — make caller-directory resolution emit-invariant (#1539)
Follow-up to #1536, which pinned `useDefineForClassFields: false` in the
js-sdk tsconfig because raising `target` to `es2022` flips the default
to `true`, and that broke the template builder. This PR fixes the root
cause and removes the pin, so the SDK now compiles with the standard
es2022 `[[Define]]` class-field semantics.

## Root cause

`TemplateBase` resolved its default `fileContextPath` in a **class field
initializer**:

```ts
private fileContextPath: PathLike =
  runtime === 'browser' ? '.' : (getCallerDirectory(STACK_TRACE_DEPTH) ?? '.')
```

With native class fields (define semantics), V8 evaluates field
initializers in an extra `<instance_members_initializer>` stack frame:

```
at getCallerDirectory (utils.ts)
at <instance_members_initializer> (index.ts)   ← extra frame under define semantics
at new TemplateBase (index.ts)
at Template (index.ts)
at user code                                    ← fixed-depth walk lands one frame short
```

`getCallerDirectory` walks the stack at a fixed depth, so it landed on
the SDK's own `src/template` directory instead of the caller's —
`.copy('folder/*', …)` then globbed against the wrong base dir (`Error:
No files found in .../src/template/...`), and the resulting client-side
failure mis-attributed build-step stack traces (the two
`stacktrace.test.ts` failures were cascades of this one bug).

## Fix

Move the default resolution into the constructor body, where the stack
shape is identical under both emits:

```ts
constructor(options?: TemplateOptions) {
  this.fileContextPath =
    options?.fileContextPath ??
    (runtime === 'browser' ? '.' : (getCallerDirectory(STACK_TRACE_DEPTH) ?? '.'))
```

The call is now emit-invariant (same `STACK_TRACE_DEPTH`), so the
tsconfig pin is removed. The method-level `getCallerFrame` call sites
were never affected — method bodies don't change shape with class-field
semantics.

Only the js-sdk is touched: the Python SDKs resolve the caller via
`inspect` and don't have this failure mode, and the CLI bundle doesn't
include `TemplateBase`.

## Usage example

Fixes relative-path resolution for SDK consumers whose toolchain emits
native class fields (e.g. esbuild/vitest with `target: es2022+`):

```ts
// user-project/scripts/template.ts
const template = Template()
  .fromBaseImage()
  .copy('assets/*', '/app/assets') // now resolves against user-project/scripts/,
                                   // not the SDK's own directory
```

## Verification

- `tests/template/stacktrace.test.ts` — 30/30 pass with the flag
defaulted (`true`), and still 30/30 when explicitly set back to `false`
(emit-invariance)
- `tests/template/build.test.ts` — 4/4 pass against the real backend
(real `.copy` glob + build)
- Smoke-tested built `dist/index.mjs` and `dist/index.js` from an
external directory: `fileContextPath` resolves to the importing script's
directory in both
- Unit project A/B: identical results with and without this change
(remaining failures are pre-existing `E2B_API_KEY`-gated live tests)
- `pnpm run typecheck`, `lint`, `format` 

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 05:01:53 -07:00
github-actions[bot] dbc6bfa161 [skip ci] Release new versions 2026-07-13 15:42:35 +00:00
Mish Ushakov 09e12b3f65 feat(sdk): set-once integration attribution via ConnectionConfig.setIntegration (#1524)
Replaces the per-call `integration` connection option with a set-once,
process-wide setter — `ConnectionConfig.setIntegration()` in JS and
`ConnectionConfig.set_integration()` in Python — so integrations
wrapping the SDK tag themselves once at startup and every request
carries the identifier in the `User-Agent` header, with no threading
through individual SDK calls. The setter is internal and hidden from
generated docs; the `integration` option is removed from
`ConnectionConfigOpts` (kept as a deprecated alias of `ConnectionOpts`)
and from the Python constructor, and the round-trip machinery from #1459
is no longer needed since rebuilt configs read the process-wide value.
User-Agent handling now follows a single rule in both SDKs via one
shared helper per SDK: an explicitly provided `User-Agent` always wins,
otherwise the SDK sends its own tagged with the current integration —
and SDK-built values are recomputed whenever a config is rebuilt, so
clearing or changing the integration propagates. Tests cover
attribution, clearing, config rebuilds, and custom User-Agent precedence
in both SDKs, with changesets for `e2b` and `@e2b/python-sdk` (minor).
CLI attribution using this setter will follow in a separate PR.

Usage (internal integrations only):

```ts
import { ConnectionConfig } from 'e2b'
ConnectionConfig.setIntegration('e2b-code-interpreter/0.1.0') // once at startup
```

```python
from e2b import ConnectionConfig
ConnectionConfig.set_integration("e2b-code-interpreter/0.1.0")  # once at startup
```

A caller-supplied `User-Agent` (via `headers`/`apiHeaders`) is preserved
in both SDKs:

```ts
const sbx = await Sandbox.create({ apiHeaders: { 'User-Agent': 'my-app/1.0' } })
// requests carry: my-app/1.0
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 15:52:09 +02:00
Mish Ushakov 07041ccffc test: skip live volume tests unless ENABLE_VOLUME_TESTS is set (#1526)
Live volume tests create real volumes against the API; this gates them
behind an `ENABLE_VOLUME_TESTS` env var so they skip by default. In the
JS SDK, the `volumeTest` fixture is chained with
`.skipIf(process.env.ENABLE_VOLUME_TESTS === undefined)`, skipping all
of `tests/volume/file.test.ts`. In the Python SDK, the `volume` and
`async_volume` fixtures call `pytest.skip` when the env var is unset,
gating `tests/{sync/volume_sync,async/volume_async}/test_file.py`.
Mocked and unit volume tests (msw-based `volume.test.ts`,
`test_volume.py`, `test_volume_content.py`, `test_volume_client.py`,
`test_volume_connection_config.py`) still run unconditionally. To run
the live tests: `ENABLE_VOLUME_TESTS=1 pnpm run test` or
`ENABLE_VOLUME_TESTS=1 poetry run pytest`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 10:38:05 -07:00
Mish Ushakov 0bd06d86d2 chore(js-sdk,cli): modernize tsconfig and adopt TypeScript 7 (side-by-side) (#1536)
Supersedes #1516 (same modernization at TypeScript 6.0). Rebased onto
`main` now that the build runs on **tsdown** (#1515).

## What & why

Adopt **TypeScript 7** for both packages and modernize the compiler
config.

TypeScript 7.0's native compiler [ships no programmatic API
yet](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#running-side-by-side-with-typescript-6.0)
(it lands in 7.1), so anything built on the TS compiler API breaks on it
— here that's tsdown's `.d.ts` generation and the codegen scripts
(`openapi-typescript`, `json-schema-to-typescript`). Per the official
guidance, TS 7 is installed **side-by-side** with TS 6:

```json
"@typescript/native": "npm:typescript@^7.0.2",      // native tsc — used for type-checking
"typescript": "npm:@typescript/typescript6@^6.0.2"  // TS6 w/ compiler API — used by tooling
```

- `tsc --noEmit` (typecheck) → **native TypeScript 7.0.2** (verified:
`tsc --version` → 7.0.2)
- `import 'typescript'` → **TypeScript 6.0** *with* the compiler API →
tsdown dts + codegen keep working
- Bonus: tsdown's dts no longer prints the "TypeScript 7.0 does not yet
have a stable API and is experimental" warning (it's on the 6.0 API now)

**Internal build-config change only — no public API or runtime behavior
changes.**

## Compiler options: before → after

### `packages/js-sdk/tsconfig.json`
| option | before | after |
|---|---|---|
| `target` | `es6` | `es2022` |
| `lib` | `["dom","ESNext"]` | `["dom","es2022"]` |
| `module` | _(unset)_ | `esnext` |
| `moduleResolution` | `node` | `bundler` |
| `allowJs` | `true` | **removed** (no `.js` sources) |
| `allowSyntheticDefaultImports` | `true` | **removed** (implied by
`esModuleInterop`) |
| `useDefineForClassFields` | _(false, implied by es6)_ | **`false` (now
explicit)** — see note |

### `packages/cli/tsconfig.json`
| option | before | after |
|---|---|---|
| `moduleResolution` | `node` | `bundler` |
| `strictNullChecks`, `strictFunctionTypes`, `strictBindCallApply`,
`strictPropertyInitialization`, `noImplicitThis`, `alwaysStrict` |
`true` | **removed** (implied by `strict`) |
| `downlevelIteration` | `true` | **removed** (removed in TS 7; no-op at
`es2022`) |
| `baseUrl` | `"."` | **removed** (removed in TS 7) |
| `paths` | `{ e2b }` | `{ src, "src/*", e2b }` (replaces `baseUrl` for
the existing `src/...` import style) |
| `outDir` | `"dist"` | **removed** (unused under `tsc --noEmit`) |
| `exclude` | _(none)_ | `["dist","node_modules"]` (so the built bundle
is never type-checked) |

`target`/`lib` for the CLI were already `es2022`.

## Notes / decisions

- **Why side-by-side, not a plain `typescript@7` bump:** TS 7.0 is the
native (Go) compiler rewrite — feature-identical to 6.0 for
type-checking, no programmatic API until 7.1. A plain bump crashed both
codegen tools (`Cannot read properties of undefined (reading
'createKeywordTypeNode')`). Side-by-side gives native-TS-7 checking
while keeping the TS-6 API for tooling. Once 7.1 ships the API and the
tools update, this collapses back to a single `typescript@7` dep.
- **`useDefineForClassFields: false` is pinned explicitly.** Raising
js-sdk's `target` to `es2022` flips this default to `true`, changing
class-field emit and shifting stack frames. The template builder
resolves the caller's directory and per-step traces via **fixed-depth**
stack walking (`getCallerDirectory` in `src/template/index.ts`), so the
extra frames threw it off by one — resolving `.copy('folder/*', …)`
against the wrong base dir and mis-attributing build steps
(`tests/template/build.test.ts` + `stacktrace.test.ts`). Pinning `false`
keeps the exact pre-existing field semantics (es6 already implied
`false`); adopting `define` semantics should be a separate, deliberately
tested change.
- **Target stays at `es2022`, not `es2023`.** `engines` still allow Node
20 (`>=20.18.1 <21 || >=22`).
- **`moduleResolution: "bundler"`** typechecks + builds cleanly in both
packages. The CLI's `baseUrl`-based bare imports (`from 'src/user'`,
`from 'src'`) are preserved via `paths`; the bundled output still
resolves them (build verified, binary smoke-tested).

## Not done (intentionally)

- **`verbatimModuleSyntax`** — ~177 `import type` conversions; left as a
follow-up.
- **Shared `tsconfig.base.json`** — the two configs diverge too much to
factor out cleanly.

## Verification
- `pnpm run typecheck`  both packages, on **native TS 7.0.2**
- `pnpm run build`  both packages (js-sdk ESM + CJS + **DTS**; cli CJS;
binary smoke-tested)
- codegen  `openapi-typescript` + `json2ts` run and produce identical
output (idempotent)
- `pnpm run lint`  both packages
- `pnpm run test` — `template/build` + `template/stacktrace` now pass
(`stacktrace` verified locally 30/30); remaining local failures are all
`E2B_API_KEY`-gated live tests, unaffected by this change

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 18:35:15 +00:00
Mish Ushakov 49367c8491 build: switch from tsup to tsdown (#1515)
Switches the build tooling for `packages/js-sdk` and `packages/cli` from
`tsup` (esbuild) to `tsdown` (rolldown), replacing each `tsup.config.js`
with a `tsdown.config.ts` and updating the `build`/`dev` scripts and
devDependencies. The published artifact layout is intentionally
unchanged — the SDK still ships `dist/index.js` (CJS), `dist/index.mjs`
(ESM) and `dist/index.d.ts`/`.d.mts`, and the CLI still ships an
executable `dist/index.js` plus `dist/templates` — kept identical via
`fixedExtension: false`. CLI dependency bundling is preserved by mapping
the old `noExternal` to tsdown's `deps.alwaysBundle` (still excluding
the ESM-only, dynamically-imported `inquirer`), and template copying
moves from an `onSuccess` shell step to tsdown's `copy` option.

Also aligns Node versions: `engines.node` for both packages is set to
`20 || >=22`, the CLI build targets `node20`, and the pinned `nodejs` in
`.tool-versions` is bumped to `22.11.0`. The large `pnpm-lock.yaml` diff
is expected — it swaps the tsup/esbuild dependency tree for tsdown's
rolldown tree (no lockfile format change).

## Verification
- Both packages build cleanly with output filenames identical to the
previous tsup builds.
- `typecheck`, `lint` (oxlint) and `build` pass for both packages; the
built CLI runs (`--version`).
- Built js-sdk imports correctly in both CJS (`require`) and ESM
(`import`), exposing the default `Sandbox` export and all named exports.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 19:04:03 +02:00
Mish Ushakov e6c4e7e9d5 chore(js): modernize Connect/Protobuf and React test deps (#1512)
## What

Modernizes the JS SDK's dependencies while remaining fully compatible
with the current supported Node range (`>=20.18.1`) — no engine changes
and no breaking impact for consumers.

- **`@connectrpc/connect` / `@connectrpc/connect-web`:** `2.0.0-rc.3` →
`^2.1.2` (off the pre-release pin onto the stable line, and switched to
a `^` range).
- **`@bufbuild/protobuf`:** `^2.6.2` → `^2.12.1`.
- **React test deps:** `react` / `@types/react` → `^19.2.0`, and
`react-dom` / `@types/react-dom` added at `^19.2.0` (previously
auto-installed as v18 peers). Dev/test-only — no runtime impact.
- **CI:** standardized `actions/setup-node` (mixed v3/v4/v6) to `v6`
across all workflows; the three `@v3` uses were on the deprecated Node16
action runtime.

No public SDK API changes — the sandbox filesystem and command RPCs use
the same Connect transport configuration.

## Why undici / Node floor were dropped from this PR

An earlier revision also bumped `undici` 7 → 8 and raised the Node floor
to `>=22.19.0`. Usage data shows **Node 20 is still the single largest
SDK runtime (~39% of sandbox creations)**, so dropping it would break
the largest consumer segment via `engine-strict` install failures.
undici 8 was the *only* change forcing Node 22, and undici `7.28.0`
(already the latest 7.x) supports Node 20 — so undici stays at `^7.28.0`
and the engine floor is unchanged. undici 8 is a good candidate for a
future major once Node 20 usage declines.

## Verification

- typecheck, lint (oxlint), and build pass
- 22 mocked Connect/undici transport unit tests pass
- 106 live filesystem/command tests pass over connectrpc `2.1.2` +
undici `7.28.0`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 16:37:50 +00:00
github-actions[bot] 0feb926937 [skip ci] Release new versions 2026-07-08 13:37:26 +00:00
Mish Ushakov 2b7dd17f10 feat(sdk): add gzip option to template copy layer (#1482)
Adds a `gzip` option to the template `.copy()` / `copyItems` layer that
controls whether copied files are gzipped before upload, threaded from
the copy call through the build-time tar stream in both the JS SDK and
the sync/async Python SDKs. It is enabled by default to preserve
existing behavior, so passing `gzip: false` (`gzip=False`) uploads an
uncompressed tar — useful for already-compressed payloads where gzip
adds CPU cost without shrinking the upload. The option name matches
node-tar's own `gzip` option and the existing sandbox filesystem `gzip`
kwarg. Gzip is deliberately excluded from the file cache hash, so
toggling it does not bust the build cache. Tests in both SDKs were
updated for the new argument and extended with `gzip: false` cases
asserting the archive is not gzipped yet still extracts, and a changeset
(`minor` for both packages) is included.

> [!NOTE]
> The server that extracts these uploaded archives lives in another repo
and must auto-detect compression (peek the gzip `0x1f 0x8b` magic)
rather than assuming gzip; confirm it handles plain tars before release.

## Usage

```ts
// JS/TS
template.copy('model.bin', '/app/', { gzip: false })
template.copyItems([{ src: 'a.bin', dest: '/app/', gzip: false }])
```

```python
# Python (sync & async)
template.copy('model.bin', '/app/', gzip=False)
template.copy_items([{ 'src': 'a.bin', 'dest': '/app/', 'gzip': False }])
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 11:50:21 +02:00
Mish Ushakov a39db3bb36 chore: switch from eslint to oxlint (#1514)
Replaces ESLint (and its `@typescript-eslint/*` and `unused-imports`
plugins) with [oxlint](https://oxc.rs) across the `js-sdk` and `cli`
packages. A root `.oxlintrc.json` replaces the three `.eslintrc.cjs`
files, the package `lint` scripts now run `oxlint`, the related
devDependencies are swapped for `oxlint`, and the lint CI path filter is
updated accordingly. Formatting rules
(`quotes`/`semi`/`linebreak-style`) are dropped because Prettier already
enforces them, and `no-unused-vars` is set to error to preserve the
previous unused-imports check. The one behavior change is that
`@typescript-eslint/member-ordering` has no oxlint equivalent and is no
longer enforced. `lint`, `typecheck`, and `prettier` all pass clean for
both packages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:34:39 +02:00
Mish Ushakov c385566c29 fix(python-sdk): correct Sandbox.list() docstring (also lists paused) (#1511)
Integration branch PR for #1500. Merges the docstring fix into `main`.

Once #1500 is merged into `python-sdk-list-docstring-base`, this PR will
carry those changes into `main`.

---------

Co-authored-by: Leinux <tristone13th@outlook.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 12:10:33 +00:00
Matt Brockman f160f08c7b Keep integration attribution on connection config (#1459)
moves integration attirbution to more private thing to avoid confusing people with first class kwargs
2026-06-26 18:30:35 -07:00