Commit Graph

5003 Commits

Author SHA1 Message Date
github-actions[bot] 9ef3f1dbbe [skip ci] Release new versions @e2b/cli@2.16.1 @e2b/python-sdk@2.36.0 e2b@2.37.0 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 b54e8d10df ci: add an httpbin template and a dropdown to pick which one to build (#1632)
## Problem

The firewall transform tests assert header injection by curling
`httpbin.e2b.team`, an externally hosted service the suite has to keep
alive. Transforms are applied by the egress proxy on the way *out* of a
sandbox, so the target has to be publicly reachable — which rules out a
CI service container, but not another sandbox.

## Change

Adds a `httpbin` template (`templates/httpbin`): go-httpbin on
`debian:bookworm-slim`, SHA256-pinned against the release
`checksums.txt` the way `templates/base` pins its Node install. Neither
official image can serve as an E2B base — `ghcr.io/mccutchen/go-httpbin`
is distroless (no shell, and `dockerfileParser.ts:79` rejects
multi-stage so the binary can't be copied out), and
`kennethreitz/httpbin` is Ubuntu 18.04 whose build fails on E2B's
`fuse3` install (both verified by building).

`templates.yml` gains a `template` choice input (`all` / `base` /
`httpbin`) rather than a second near-identical workflow file. `all` is
the default so a plain dispatch behaves as before, and the DockerHub
image job is skipped for `httpbin`, which has no image counterpart.

The template stays **private to the E2B team**, like `base` — publishing
would only expose it to other projects, and the tests that spawn it use
our API key anyway. The alias is passed unprefixed because the server
namespaces it with the team slug, so the name the tests resolve is
**`e2b/httpbin`**; only `base` predates namespacing and stays bare.

Merge this, then dispatch **Build and push prepared templates** with
`template: httpbin` — #1631 stacks on top and resolves the template as
`e2b/httpbin`.

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>

SDK-304

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-30 23:06:40 +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
Mish Ushakov 86934c779c ci(js-sdk): make the Cloudflare deploy test leg advisory (#1622)
## Problem

The `cloudflare-deploy` leg deploys to a brand-new Cloudflare preview
account on every run, so it inherits that account's propagation and
read-after-write races — the fresh `workers.dev` subdomain 404s until
the route reaches the edge, and the subdomain API can 404 the script it
just accepted (`This Worker does not exist on your account [code:
10007]`). That fails ~1 run in 8 (6 of ~46 runs since 2026-07-24)
without saying anything about the SDK, and the job gates both the
required `SDK Tests Status` check and the release workflow's `publish`
step — both of today's release runs were blocked by it
([30473411814](https://github.com/e2b-dev/E2B/actions/runs/30473411814),
[30474175682](https://github.com/e2b-dev/E2B/actions/runs/30474175682)).

## Fix

`continue-on-error` on that matrix leg only, so it still runs and still
reports on every PR but no longer blocks merges or releases. The signal
survives: a genuine bundle regression (e.g. the #1579 Workers startup
crash) is rejected at upload deterministically, not intermittently.

Follow-ups to make the leg reliably green again: #1592 (propagation
poll, still open) plus a retry around `wrangler deploy --temporary` for
the 10007 race.

CI-only change — no changeset, no user-facing surface.

Closes SDK-301

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-30 09:30:35 -07:00
dependabot[bot] 45d26792f1 chore(deps-dev): bump datamodel-code-generator from 0.34.0 to 0.64.0 in /packages/python-sdk in the uv group across 1 directory (#1621)
> [!NOTE]
> Manual follow-up commit on top of Dependabot's bump (addressing review
feedback): the codegen image pin was out of sync, so this PR also bumps
it and carries the regenerated output.

### Manual changes on top of the bump

- `codegen.Dockerfile` bumped from `datamodel-code-generator==0.34.0` to
`0.64.0`. `pyproject.toml`'s `codegen` group and the Dockerfile must
stay in sync (the comment above the group says so) — CI's `Generated
files` check regenerates from the image, so leaving the image at
`0.34.0` would make `make init` produce output CI rejects.
- `packages/python-sdk/e2b/sandbox/mcp.py` regenerated with `0.64.0`.
Two output changes:
- builtin generics (`list[str]`, `dict[str, Any]` instead of
`List`/`Dict`), fine on the SDK's `>=3.10` floor;
- `additionalProperties: false` in `spec/mcp-server.json` is now honored
as PEP 728 `closed=True` (`0.34.0` silently dropped it), and `TypedDict`
is imported from `typing_extensions` accordingly.
- `typing-extensions>=4.1.0` → `>=4.10.0`. `closed=True` is evaluated at
class-creation time, i.e. on `import e2b`, and 4.10.0 is the first
release whose `TypedDict` accepts the keyword (4.9.0 raises `TypeError:
_TypedDictMeta.__new__() got an unexpected keyword argument 'closed'`).
- Changeset added (`patch` for `@e2b/python-sdk`), since the regenerated
file and the dependency floor ship to users.

Nothing changes for callers at runtime — `McpServer` is still a plain
dict at the call site:

```python
from e2b import Sandbox

sbx = Sandbox.create(mcp={"duckduckgo": {}, "brave": {"braveApiKey": "..."}})
```

The `closed` types are also inert for type checkers in practice, because
the public `McpServer` is `Union[BaseMcpServer, GitHubMcpServer]` and
the second arm is a `Dict[str, ...]`. Verified: `pyright` and `mypy`
both clean against the snippet above, `ruff`/`ty`/`pnpm typecheck`
clean, 283 offline Python unit tests pass, and regenerating with the
full pinned toolchain (`python:3.10` + `black==26.3.1` + the other
Dockerfile pins) reproduces the committed file byte-for-byte.

---

Bumps the uv group with 1 update in the /packages/python-sdk directory:
[datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator).

Updates `datamodel-code-generator` from 0.34.0 to 0.64.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/koxudaxi/datamodel-code-generator/releases">datamodel-code-generator's
releases</a>.</em></p>
<blockquote>
<h2>0.64.0</h2>
<h2>Breaking Changes</h2>
<h3>Code Generation Changes</h3>
<ul>
<li>Self-referencing fields are now quoted with
<code>--disable-future-imports</code> - When
<code>--disable-future-imports</code> is set (no <code>from __future__
import annotations</code> and no native PEP 649 deferred evaluation on
Python &lt; 3.14), self-referencing and forward-referencing field
annotations in regular <code>BaseModel</code> classes are now emitted as
quoted forward references instead of bare names. Previously such
annotations were left unquoted, producing invalid code that raised
<code>NameError</code> (Ruff F821) at class-evaluation time. Output for
the common case (with <code>from __future__ import annotations</code> or
Python 3.14 native deferred annotations) is unchanged. Users who
snapshot/golden-file generated output for the
<code>--disable-future-imports</code> configuration with
self-referencing models will see the annotation change from unquoted to
quoted, e.g. <code>children: Optional[List[Node]]</code> →
<code>children: Optional[List[&quot;Node&quot;]]</code>. (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3387">#3387</a>)</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Update CHANGELOG for 0.63.0 by <a
href="https://github.com/dcg-generated-docs"><code>@​dcg-generated-docs</code></a>[bot]
in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3345">koxudaxi/datamodel-code-generator#3345</a></li>
<li>Deduplicate module content builder by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3346">koxudaxi/datamodel-code-generator#3346</a></li>
<li>Deduplicate import reference helpers by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3348">koxudaxi/datamodel-code-generator#3348</a></li>
<li>Refactor jsonschema root model registration by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3352">koxudaxi/datamodel-code-generator#3352</a></li>
<li>Refactor XML Schema literal helpers by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3349">koxudaxi/datamodel-code-generator#3349</a></li>
<li>Move builtin formatter helpers by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3351">koxudaxi/datamodel-code-generator#3351</a></li>
<li>Deduplicate Pydantic v2 config helpers by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3350">koxudaxi/datamodel-code-generator#3350</a></li>
<li>Deduplicate DataType type hint rendering by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3354">koxudaxi/datamodel-code-generator#3354</a></li>
<li>Fix <code>constr()</code> for string fields carrying
minItems/maxItems by <a
href="https://github.com/DarkaMaul"><code>@​DarkaMaul</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3353">koxudaxi/datamodel-code-generator#3353</a></li>
<li>Cover non-finite import idempotence by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3367">koxudaxi/datamodel-code-generator#3367</a></li>
<li>Deduplicate input text detection by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3357">koxudaxi/datamodel-code-generator#3357</a></li>
<li>Remove stale protobuf coverage pragma by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3358">koxudaxi/datamodel-code-generator#3358</a></li>
<li>Cover explicit null OpenAPI media schemas by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3360">koxudaxi/datamodel-code-generator#3360</a></li>
<li>Simplify Python version feature checks by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3361">koxudaxi/datamodel-code-generator#3361</a></li>
<li>Speed up CI checks by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3378">koxudaxi/datamodel-code-generator#3378</a></li>
<li>Add maintainer link to docs footer and README by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3379">koxudaxi/datamodel-code-generator#3379</a></li>
<li>Use builtin formatter in CI by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3380">koxudaxi/datamodel-code-generator#3380</a></li>
<li>Split coverage by OS by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3381">koxudaxi/datamodel-code-generator#3381</a></li>
<li>Simplify import removal cleanup by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3362">koxudaxi/datamodel-code-generator#3362</a></li>
<li>Pin deprecation warning stacklevel by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3363">koxudaxi/datamodel-code-generator#3363</a></li>
<li>Pin public module exports by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3364">koxudaxi/datamodel-code-generator#3364</a></li>
<li>Cover to_hashable branch cases by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3366">koxudaxi/datamodel-code-generator#3366</a></li>
<li>Cover stable toposort behavior by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3369">koxudaxi/datamodel-code-generator#3369</a></li>
<li>Extract registry render helpers by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3371">koxudaxi/datamodel-code-generator#3371</a></li>
<li>Fix minItems for arrays of URI strings by <a
href="https://github.com/sjh9714"><code>@​sjh9714</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3377">koxudaxi/datamodel-code-generator#3377</a></li>
<li>Deduplicate config value validators by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3372">koxudaxi/datamodel-code-generator#3372</a></li>
<li>Cover CLI option metadata helpers by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3374">koxudaxi/datamodel-code-generator#3374</a></li>
<li>Cover Pydantic v2 version fallback by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3368">koxudaxi/datamodel-code-generator#3368</a></li>
<li>Fix nullable JSON Schema const enums by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3355">koxudaxi/datamodel-code-generator#3355</a></li>
<li>Pin patchable generation seams by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3365">koxudaxi/datamodel-code-generator#3365</a></li>
<li>Cover utility helper behavior by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3375">koxudaxi/datamodel-code-generator#3375</a></li>
<li>Cover DefaultPutDict behavior by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3376">koxudaxi/datamodel-code-generator#3376</a></li>
<li>Cover validator config normalization by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3373">koxudaxi/datamodel-code-generator#3373</a></li>
<li>Avoid expensive runtime type checks by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3382">koxudaxi/datamodel-code-generator#3382</a></li>
<li>Avoid eager builtin formatter import by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3383">koxudaxi/datamodel-code-generator#3383</a></li>
<li>Avoid eager TOML parser import by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3384">koxudaxi/datamodel-code-generator#3384</a></li>
<li>Stabilize msgspec payload tests by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3385">koxudaxi/datamodel-code-generator#3385</a></li>
<li>Avoid eager input parser imports by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3386">koxudaxi/datamodel-code-generator#3386</a></li>
<li>Avoid eager parser model imports by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3388">koxudaxi/datamodel-code-generator#3388</a></li>
<li>Avoid eager AsyncAPI converter imports by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3389">koxudaxi/datamodel-code-generator#3389</a></li>
<li>Dispose parser on parse errors by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3390">koxudaxi/datamodel-code-generator#3390</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/koxudaxi/datamodel-code-generator/blob/main/CHANGELOG.md">datamodel-code-generator's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/koxudaxi/datamodel-code-generator/releases/tag/0.64.0">0.64.0</a>
- 2026-06-14</h2>
<h2>Breaking Changes</h2>
<h3>Code Generation Changes</h3>
<ul>
<li>Self-referencing fields are now quoted with
<code>--disable-future-imports</code> - When
<code>--disable-future-imports</code> is set (no <code>from __future__
import annotations</code> and no native PEP 649 deferred evaluation on
Python &lt; 3.14), self-referencing and forward-referencing field
annotations in regular <code>BaseModel</code> classes are now emitted as
quoted forward references instead of bare names. Previously such
annotations were left unquoted, producing invalid code that raised
<code>NameError</code> (Ruff F821) at class-evaluation time. Output for
the common case (with <code>from __future__ import annotations</code> or
Python 3.14 native deferred annotations) is unchanged. Users who
snapshot/golden-file generated output for the
<code>--disable-future-imports</code> configuration with
self-referencing models will see the annotation change from unquoted to
quoted, e.g. <code>children: Optional[List[Node]]</code> →
<code>children: Optional[List[&quot;Node&quot;]]</code>. (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3387">#3387</a>)</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Update CHANGELOG for 0.63.0 by <a
href="https://github.com/dcg-generated-docs"><code>@​dcg-generated-docs</code></a>[bot]
in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3345">koxudaxi/datamodel-code-generator#3345</a></li>
<li>Deduplicate module content builder by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3346">koxudaxi/datamodel-code-generator#3346</a></li>
<li>Deduplicate import reference helpers by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3348">koxudaxi/datamodel-code-generator#3348</a></li>
<li>Refactor jsonschema root model registration by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3352">koxudaxi/datamodel-code-generator#3352</a></li>
<li>Refactor XML Schema literal helpers by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3349">koxudaxi/datamodel-code-generator#3349</a></li>
<li>Move builtin formatter helpers by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3351">koxudaxi/datamodel-code-generator#3351</a></li>
<li>Deduplicate Pydantic v2 config helpers by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3350">koxudaxi/datamodel-code-generator#3350</a></li>
<li>Deduplicate DataType type hint rendering by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3354">koxudaxi/datamodel-code-generator#3354</a></li>
<li>Fix <code>constr()</code> for string fields carrying
minItems/maxItems by <a
href="https://github.com/DarkaMaul"><code>@​DarkaMaul</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3353">koxudaxi/datamodel-code-generator#3353</a></li>
<li>Cover non-finite import idempotence by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3367">koxudaxi/datamodel-code-generator#3367</a></li>
<li>Deduplicate input text detection by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3357">koxudaxi/datamodel-code-generator#3357</a></li>
<li>Remove stale protobuf coverage pragma by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3358">koxudaxi/datamodel-code-generator#3358</a></li>
<li>Cover explicit null OpenAPI media schemas by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3360">koxudaxi/datamodel-code-generator#3360</a></li>
<li>Simplify Python version feature checks by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3361">koxudaxi/datamodel-code-generator#3361</a></li>
<li>Speed up CI checks by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3378">koxudaxi/datamodel-code-generator#3378</a></li>
<li>Add maintainer link to docs footer and README by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3379">koxudaxi/datamodel-code-generator#3379</a></li>
<li>Use builtin formatter in CI by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3380">koxudaxi/datamodel-code-generator#3380</a></li>
<li>Split coverage by OS by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3381">koxudaxi/datamodel-code-generator#3381</a></li>
<li>Simplify import removal cleanup by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3362">koxudaxi/datamodel-code-generator#3362</a></li>
<li>Pin deprecation warning stacklevel by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3363">koxudaxi/datamodel-code-generator#3363</a></li>
<li>Pin public module exports by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3364">koxudaxi/datamodel-code-generator#3364</a></li>
<li>Cover to_hashable branch cases by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3366">koxudaxi/datamodel-code-generator#3366</a></li>
<li>Cover stable toposort behavior by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3369">koxudaxi/datamodel-code-generator#3369</a></li>
<li>Extract registry render helpers by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3371">koxudaxi/datamodel-code-generator#3371</a></li>
<li>Fix minItems for arrays of URI strings by <a
href="https://github.com/sjh9714"><code>@​sjh9714</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3377">koxudaxi/datamodel-code-generator#3377</a></li>
<li>Deduplicate config value validators by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3372">koxudaxi/datamodel-code-generator#3372</a></li>
<li>Cover CLI option metadata helpers by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3374">koxudaxi/datamodel-code-generator#3374</a></li>
<li>Cover Pydantic v2 version fallback by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3368">koxudaxi/datamodel-code-generator#3368</a></li>
<li>Fix nullable JSON Schema const enums by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3355">koxudaxi/datamodel-code-generator#3355</a></li>
<li>Pin patchable generation seams by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3365">koxudaxi/datamodel-code-generator#3365</a></li>
<li>Cover utility helper behavior by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3375">koxudaxi/datamodel-code-generator#3375</a></li>
<li>Cover DefaultPutDict behavior by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3376">koxudaxi/datamodel-code-generator#3376</a></li>
<li>Cover validator config normalization by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3373">koxudaxi/datamodel-code-generator#3373</a></li>
<li>Avoid expensive runtime type checks by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3382">koxudaxi/datamodel-code-generator#3382</a></li>
<li>Avoid eager builtin formatter import by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3383">koxudaxi/datamodel-code-generator#3383</a></li>
<li>Avoid eager TOML parser import by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3384">koxudaxi/datamodel-code-generator#3384</a></li>
<li>Stabilize msgspec payload tests by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3385">koxudaxi/datamodel-code-generator#3385</a></li>
<li>Avoid eager input parser imports by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3386">koxudaxi/datamodel-code-generator#3386</a></li>
<li>Avoid eager parser model imports by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3388">koxudaxi/datamodel-code-generator#3388</a></li>
<li>Avoid eager AsyncAPI converter imports by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3389">koxudaxi/datamodel-code-generator#3389</a></li>
<li>Dispose parser on parse errors by <a
href="https://github.com/koxudaxi"><code>@​koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3390">koxudaxi/datamodel-code-generator#3390</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/53a25ab8ddb132ac68a2795247fc855b8f445d84"><code>53a25ab</code></a>
Fast path schema output (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3410">#3410</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/ee2087f32e6100f5c3642e7ea8506aa38e9df26c"><code>ee2087f</code></a>
Skip discriminator import scan (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3411">#3411</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/bdf5ddfc27f94a06ba8d289759193bb09daadd34"><code>bdf5ddf</code></a>
fix: quote self-referencing fields when --disable-future-imports is set
(<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3387">#3387</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/ad4ec877fa6708baebdaaf820171d24bfe5bf0cb"><code>ad4ec87</code></a>
Cache payload validation strategies (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3409">#3409</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/b191d52a0a1d83edeac9553119f70b2f5c131126"><code>b191d52</code></a>
Shard Python tests (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3408">#3408</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/29dd6d74c95dd7799d51f2c707db24862809eb23"><code>29dd6d7</code></a>
Cache parsed sources (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3407">#3407</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/93e2fe3cf5774d5e4d2083fac365e5bcbf0a647a"><code>93e2fe3</code></a>
Defer generation refresh (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3406">#3406</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/bb01d9c628f9077cc5dd72320ae60a18abd5b790"><code>bb01d9c</code></a>
Lazy root format exports (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3405">#3405</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/48237ed8c3af3bb58b5e6b274925ebb646412d3a"><code>48237ed</code></a>
Fast path JSON schemas (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3404">#3404</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/b21d106c88ac22f137cd4562389ad95a50c2e912"><code>b21d106</code></a>
Slot generation facts (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3403">#3403</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/koxudaxi/datamodel-code-generator/compare/0.34.0...0.64.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=datamodel-code-generator&package-manager=uv&previous-version=0.34.0&new-version=0.64.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts page](https://github.com/e2b-dev/E2B/network/alerts).

</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mish Ushakov <10400064+mishushakov@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-30 18:15:52 +02: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 9e3e52b4fb Add --user/--cwd/--env terminal flags to sandbox create & connect (#1501)
Exposes `--user`, `--cwd`, and repeatable `--env KEY=VALUE` flags on
`e2b sandbox create` (and the deprecated `spawn` alias) and `e2b sandbox
connect`, forwarding them to the underlying PTY session so the connected
terminal starts as the given user, in the given working directory, and
with the given environment variables. The SDKs already supported these
PTY options — this just wires them through the CLI. The `--env` arg
parser is extracted into a shared `src/utils/env.ts` and reused across
`create`, `connect`, and `exec`. Added unit tests for the parser and CLI
tests covering the new flags; a changeset is included for `@e2b/cli`.

## Usage

```bash
# Start the terminal as root, in /app, with custom env vars
e2b sandbox create base --user root --cwd /app --env FOO=bar --env TOKEN=abc123

# Same flags when attaching to an already-running sandbox
e2b sandbox connect <sandboxID> --user root --cwd /app --env FOO=bar
```

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

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 17:33:43 -07:00
Mish Ushakov 05b7a792ff fix(ci): depend on the SDK via workspace:^ so releases tag the version bump (#1619)
Closes
[SDK-298](https://linear.app/e2b/issue/SDK-298/release-tags-point-at-the-commit-before-the-version-bump).
Replaces #1615, which moved the tags after the fact instead of removing
the reason they were misplaced.

## The bug

Every published release tag pointed at the commit *preceding* its own
version bump:

```console
$ git show '@e2b/python-sdk@2.35.0:packages/python-sdk/pyproject.toml' | head -3
[project]
name = "e2b"
version = "2.34.0"      # ← tagged 2.35.0
```

Anything that builds from a git tag rather than a registry got the
previous release: distro packagers, `pip install git+…@tag`, any bisect
over a release regression. `python3Packages.e2b` in nixpkgs shipped
1.5.0 as 1.5.1 from June 2025.

## Root cause: a dependency cycle

`changeset publish` tags whatever commit it publishes from, so the fix
is to commit the version bump first. That was impossible:

```
tag              must point at →  release commit
release commit   must contain   →  pnpm-lock.yaml
pnpm-lock.yaml   contains       →  integrity hash of a tarball this release uploads
```

`packages/cli` depended on `e2b` by registry range, so `changeset
version` rewrote that range and the lockfile had to be re-resolved
against a tarball that did not exist yet. The lockfile could only be
refreshed *after* publishing, which forced the commit — and therefore
the tags — after it too.

## The fix

`packages/cli`: `"e2b": "^2.36.1"` → `"e2b": "workspace:^"`.

The lockfile now records `link:../js-sdk` and stops changing at release
time, so the release commit is complete before anything is uploaded:

| | before | after |
|---|---|---|
| 1 | `pnpm run version` | `pnpm run version` |
| 2 | publish **+ tag** ← wrong commit | **commit** (local) |
| 3 | refresh `pnpm-lock.yaml` (retry ≤6×) | publish **+ tag** ← right
commit |
| 4 | commit + push | push |

That deletes the lockfile-refresh step and its whole
registry-propagation retry loop (#1589), and `createGithubReleases:
true` keeps doing the tagging and GitHub releases — no custom tagging
code. Keeping the commit local also improves recovery: a publish that
uploads *nothing* leaves the branch untouched with the changesets
intact, so re-dispatching retries cleanly.

### Landing that commit is now mandatory, so the push is resilient

Once the tags point at a local commit, getting it onto the branch stops
being bookkeeping. `changesets/action` pushes each tag as soon as
`changeset publish` reports it (`runPublish` → `git.pushTag`), *before*
it propagates a non-zero exit — so three things changed:

- **The push is gated on the tags themselves** — `git tag --points-at
HEAD` — not on whether the publish step succeeded. The tags are the
thing that has to end up reachable, so they are the right thing to ask.
A partial failure (npm succeeds, then python-sdk's `postPublish` fails
on PyPI) used to skip the push and strand tags on a commit that reached
no branch while `main` kept the old versions.

I first wrote this as `!cancelled() && (success() ||
steps.release.outputs.published == 'true')`, which was wrong in both
directions: `success()` fires in exactly the case that must be skipped
(publish exits 0 having uploaded nothing → pushes a bump with no tags,
cementing a version that can never be published), and `published` is
left unset when the action *throws* after tagging (`core.setOutput` runs
only on a normal return from `runPublish`, but `git.pushTag` happens
inside it) — so it was skipped in the very case it existed for. The tag
gate also covers `@e2b/python-sdk`, which the npm-derived output never
did, since `privatePackages.tag` is on.

- **A partial publish is reported, not swallowed.** It still has to land
— otherwise the pushed tags hang off no branch — but the bump is then on
the branch with the changesets consumed, so re-dispatching will not
retry what failed. The step now names the tags that did land and points
out that `postPublish`'s PyPI upload was skipped (the root script is
`changeset publish && ... postPublish`, so a non-zero npm exit
short-circuits it).
- **A non-fast-forward is reconciled with a merge,** not a rebase (which
would orphan the tags) and not a hard failure. Hard-failing left an
already-published release needing manual git surgery, and a naive
re-dispatch would publish nothing (versions already on the registry),
tag nothing, and report **success** — quietly recreating SDK-298.
- **`git add -A` replaces `commit -am`,** which cannot stage new files.
`changeset version` writes each `CHANGELOG.md` fresh, so no release
commit has ever contained one:

  ```console
  $ git show --stat cf8296cf8 | tail -4
   .changeset/lucky-pandas-wave.md    |  5 ---
   packages/cli/package.json          |  4 +-
   packages/js-sdk/package.json       |  2 +-
   pnpm-lock.yaml                     | 77 +-----------------
  ```

## The published packages do not change

`pnpm publish` (which `changeset publish` uses in a pnpm workspace)
rewrites the protocol. Verified on the real CLI package with the
workspace SDK at 9.9.9:

```
e2b dependency -> ^9.9.9
PASS: no workspace: in published manifest
```

## Verification

| check | result |
|---|---|
| `pnpm install --frozen-lockfile` on a clean clone | consistent |
| `pnpm run version` touches the lockfile? | **no** — `git diff
pnpm-lock.yaml` empty after bumping sdk 2.36.1→2.36.2, cli→2.16.1 |
| tags land on the release commit | `PASS e2b@2.36.2`, `PASS
@e2b/python-sdk@2.36.0`; tagged trees contain `"version": "2.36.2"` /
`version = "2.36.0"` |
| CLI still bumped when the SDK is | yes, `updateInternalDependencies`
still sees the internal dep |
| CLI typecheck / tests | clean / 102 passed (1 pre-existing failure
needs `E2B_API_KEY` + a built `dist`) |
| CLI bundle | builds, contains the workspace SDK, zero external
`require("e2b")` |
| `pnpm publish` git checks | `changeset publish` passes
`--no-git-checks` for pnpm ≥5 (repo pins 9.15.5); added explicitly to
the RC flows, which publish from a feature branch with an uncommitted
bump |
| `prepack` guard | `npm pack` fails and produces no tarball; `pnpm
pack` passes and rewrites to `^2.36.1` |
| `pnpm publish` lifecycle | runs `prepublishOnly` + `prepack` +
`prepare`, so the RC still builds; `pnpm pack` runs only `prepack` +
`prepare` (0.37 s, no rebuild) |
| `pnpm publish --provenance` | flag accepted (pnpm forwards to the npm
publish it spawns) |
| `pnpm link --global` | links the workspace CLI (2.16.0) and resolves
`workspace:^`; tested against an isolated `PNPM_HOME` |
| `pnpm version` / `pnpm pkg` | pnpm forwards both to npm verbatim, so
these are the same code path as before — neither resolves dependencies,
so `workspace:` is inert there |
| lockfile vs `exclude-links-from-lockfile=true` | `--frozen-lockfile`
green with the new `link:../js-sdk` entry, including after a
release-style version bump |

## Everything packs and publishes with pnpm

Only pnpm rewrites `workspace:`. `npm pack` copies the protocol into the
tarball verbatim and `npm install` then refuses it. Rather than hand-pin
the range back before each npm call, every flow that produces or
installs a CLI tarball now uses pnpm:

| flow | before | after |
|---|---|---|
| `pkg_artifacts.yml` | `npm pack` | `pnpm pack` |
| `publish_candidates.yml` | `npm publish --provenance` | `pnpm publish
--provenance --no-git-checks` |
| `.github/actions/build-cli` | `npm install -g .` | `pnpm link
--global` |

The `build-cli` action was a **third** npm consumer of the manifest,
missed on the first pass. It only worked because npm symlinks a local
directory for `-g` without resolving its dependencies at all — verified:
a control package depending on `chalk` installed with exit 0 and chalk
was never fetched. Force packing (`install_links=true`) and it dies with
`EUNSUPPORTEDPROTOCOL`.

Moving the rewrite to pack time changes *when* it resolves, which
matters in `pkg_artifacts.yml`: `pnpm pack` uses whatever version the
workspace SDK has at that moment, and that job renames the SDK to an
unpublished prerelease. Packing the CLI first was required —

```console
# SDK renamed first (wrong order)
CLI packed with e2b -> ^2.36.2-fake-branch.0   # never published → ETARGET

# CLI packed first (as merged)
e2b-cli-2.16.1-fake-branch.0.tgz -> e2b: ^2.36.1   # published, resolvable
```

`publish_candidates.yml` needs the opposite order and already had it:
the SDK RC *is* published first, so the CLI correctly pins that RC.
`--no-git-checks` is new there — candidates are cut from a feature
branch with the version bump uncommitted, so `pnpm publish` would
otherwise refuse.

### Not enforced, deliberately

I went down a path here and backed out of it, so it is worth recording.
I first added a
`prepack` guard on `packages/cli` that refused to build a tarball for
any packer but
pnpm. It had three bypasses: `npm_config_user_agent` is inherited, so
npm spawned from
pnpm still reports `pnpm/…` and sailed through it; and
`--ignore-scripts` and
`npm install -g <dir>` never run lifecycle scripts at all. I then
replaced it with a
step that installed the packed tarball with npm on every PR, which did
cover all of
those (verified: it rejects an `npm pack` tarball with
`EUNSUPPORTEDPROTOCOL` while
`pnpm pack` resolves the range to `^2.36.1`).

Both are now gone, in favour of keeping this PR to its actual subject.
So the rewrite is
unverified: the existing flows all use `pnpm pack`/`pnpm publish`, and
`changeset publish` picks pnpm by detecting the workspace, so it happens
— but nothing
catches it if a future flow reaches for npm instead. The tarball-install
step is a cheap
seven lines if we later decide we want it.

## Behavior change worth knowing

CLI tests previously resolved `e2b` from `node_modules`, i.e. the
*previously released* SDK, while `tsconfig.json` and the tsdown bundle
already used `../js-sdk/src`. `vitest.config.ts` now has a matching
alias, so all three agree and tests exercise the SDK that ships. The
alias is load-bearing — without it the workspace package's `main`
(`dist/index.js`) doesn't exist until the SDK is built:

```
Error: Failed to resolve entry for package "e2b".
⎯⎯⎯⎯⎯⎯ Failed Tests 11 ⎯⎯⎯⎯⎯⎯⎯
```

A broken SDK in the tree now fails CLI tests. `cli_tests.yml` and
`pkg_artifacts.yml` already built the SDK before the CLI, so no CI
ordering changed.

## Not retagging the past

Tags up to `e2b@2.36.1` / `@e2b/cli@2.16.0` / `@e2b/python-sdk@2.35.0`
stay off by one — moving published tags breaks anyone who pinned them.
**Build those versions from the npm tarball or the PyPI sdist, not from
the git tag.** That matters for distro packagers: `python3Packages.e2b`
in nixpkgs shipped 1.5.0 as 1.5.1 for exactly this reason. This caveat
is recorded here and in [SDK-298](https://linear.app/e2b/issue/SDK-298)
rather than in the repo.

## Follow-up

SDK-298 also notes `packages/python-sdk/pyproject.toml` pins
`uv_build>=0.10.0,<0.11.0`, so packagers on uv 0.11.x must patch it to
build at all. And `e2b-dev/code-interpreter` has the same tag bug in its
own publish workflow.

> **Corrections to earlier versions of this description:**
>
> 1. It suggested demoting `e2b` to a `devDependency` since the bundle
inlines it. That breaks the CLI — but *not* for the reason given next.
> 2. It then claimed `tsdown.config.ts` derives `alwaysBundle` from
`dependencies`, so removing `e2b` makes it *external* and the CLI ships
a bare `require("e2b")`. **That is backwards.** tsdown externalizes
exactly the production dependencies (`getProductionDeps` = `dependencies
∪ peerDependencies ∪ optionalDependencies`), so listing `e2b` there is
what would externalize it; `alwaysBundle` exists to cancel that. A
devDependency is *also* inlined. Verified with a control: moving `e2b`
to `devDependencies` still emits zero `require("e2b")`, while adding it
to `excludedPackages` is what produces the bare require and drops the
bundle from 2.14 MB to 1.84 MB.
>
> The real reason it must stay a dependency is runtime resolution: the
SDK reaches `undici`, `glob` and `tar` through `dynamicImport`, which is
deliberately opaque to bundlers, so they resolve from `node_modules` at
run time. The CLI declares none of them and gets all three via `e2b`:
>
> ```
> undici: present   undici8: present   glob: present   tar: present
> ```
>
> Without them `e2b template build` loses `glob`/`tar` and
`loadUndici()` returns `undefined`, silently downgrading every request
to the global `fetch` and giving up H2 and proxy support. So: **do not
demote `e2b` to a devDependency.** This warning lives only here — there
is no in-repo note for it.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-29 16:59:33 +00:00
Mish Ushakov 2c061eb8cf chore(cli): migrate dashboard links to project-era tab entrypoints (#1605)
## Summary

Following the dashboard's teams→projects rename (e2b-dev/dashboard#521),
this PR points the `--project` flag help at the dashboard's
`?tab=general` entrypoint (General settings, where the project ID lives)
instead of the old `?tab=team`, and adds a `@e2b/cli` patch changeset.
It also rewrites the CLI README's headless-auth note to use
`E2B_API_KEY` (the supported browserless path) instead of
`E2B_ACCESS_TOKEN`, pointing at the dashboard's API Keys tab, and drops
the now-redundant `E2B_ACCESS_TOKEN` vs `E2B_API_KEY` callout. The SDKs'
`?tab=keys` and the CLI's `?tab=personal` links stay unchanged — those
tabs remain valid entrypoints.

## Example

```
$ e2b template create --help
  -t, --project <project-id>  specify the project ID that the operation will be associated with.
                              You can find project ID in the project settings in the E2B dashboard
                              (https://e2b.dev/dashboard?tab=general).
```

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 18:21:35 +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 48e9249270 fix(cli): bump @npmcli/package-json to ^7, clearing deprecated glob@10 (#1614)
Follow-up to #1613, which fixed the `glob@11` deprecation warning in
`e2b` but left `@e2b/cli` warning via `@npmcli/package-json@5 →
glob@10`. That PR proposed `@npmcli/package-json@7`, but `^7` alone
isn't enough — 7.0.0–7.0.2 still depend on the equally deprecated
`glob@^11`, and the move to `glob@13` only landed in **7.0.4**, so this
pins `^7.0.5`. Since `@npmcli/package-json@7` requires Node `^20.17.0 ||
>=22.9.0`, the CLI's Node 22 floor moves from `>=22` to `>=22.9.0` —
matching the dependency exactly rather than excluding anyone it still
supports. Node 20 support is unchanged, since `^20.17.0` covers the
existing `>=20.18.1 <21`.

## Before / after

```console
$ npm install @e2b/cli          # before
npm warn deprecated glob@11.1.0: Old versions of glob are not supported...
npm warn deprecated glob@10.5.0: Old versions of glob are not supported...
added 183 packages in 4s

$ npm install @e2b/cli          # after (both tarballs packed locally)
added 145 packages in 1s
```

No API change. `e2b template init` is the only consumer, and the
`PackageJson.load`/`create`/`update`/`save` surface it uses is unchanged
across the bump.

## Verification

- Packed `e2b` + `@e2b/cli` and installed into a scratch project with
`overrides` pointing `e2b` at the local tarball (the post-release
state): zero deprecation warnings, `npm ls glob --all` reports only
`glob@13.0.6`.
- Ran `e2b template init -n my-tmpl -l typescript` from that packed
install against a real host `package.json` — scripts added, pre-existing
scripts preserved.
- `packages/cli` suite: 102 passed / 1 skipped, including all 14
`template init` tests, which assert on the written `package.json` in
both the `load` (existing file) and `create` (no file) branches.
`template/create.test.ts` fails identically on a clean tree in this
environment — it requires `E2B_API_KEY`.
- `pnpm run format` / `lint` / `typecheck` clean.
`@types/npmcli__package-json` stays at `^4.0.4`; v7 ships no types.
- `.tool-versions` is untouched: the pinned `nodejs 22.18.0` already
satisfies `>=22.9.0`, so CI (which derives `node-version` from that
file) needs no change.

Closes SDK-297. Follow-up to #1613 (SDK-296).

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

Co-authored-by: Claude <noreply@anthropic.com>
e2b@2.36.1 @e2b/cli@2.16.0
2026-07-27 06:18:41 -07: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>
@e2b/cli@2.15.1 @e2b/python-sdk@2.35.0 e2b@2.36.0
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 00253c39cc feat(python-sdk): migrate envd RPC to the official connectrpc client (#1558)
Replaces the vendored `e2b_connect` client and the custom Go
`protoc-gen-connect-python` plugin with the official Connect RPC client
for Python ([`connectrpc`](https://github.com/connectrpc/connect-py),
transport: `pyqwest`/Rust hyper), and switches the envd messages from
Google's `protobuf` runtime to Buf's
[`protobuf-py`](https://github.com/bufbuild/protobuf-py) (which
`connectrpc` already requires) — the SDK no longer depends on the
conflict-prone `protobuf` package at all, and the protoc binary drops
out of the codegen image. The wire format (same protos, same JSON) is
unchanged. Closing a command or watch stream early now sends
`RST_STREAM`, fixing abandoned streams leaking on the shared HTTP/2
connection, and peer resets surface as typed `ConnectError`s. The
plumbing mirrors the `e2b.api` layout: shared pieces (a JSON codec that
ignores unknown response fields, proxy narrowing, pool tuning) live in
`e2b/envd/client_shared.py`, the flavor-specific pyqwest transports
(wrapped in pyqwest's retry middleware, see the retry note below) and
`create_rpc_client` factories in `e2b/envd/client_sync/` and
`e2b/envd/client_async/`, and the default-header/logging interceptors in
`e2b/envd/interceptors.py`; `e2b/envd/rpc.py` maps `connectrpc` error
codes onto the existing SDK exceptions, so the public API is unchanged
(`sandbox.commands.run(...)`, `files.watch_dir(...)`, etc. work exactly
as before). The REST API and file upload/download keep using `httpx`.

The `proxy` connection option now applies to sandbox RPC calls too —
[pyqwest
0.7.0](https://github.com/curioswitch/pyqwest/releases/tag/v0.7.0) added
an httpx-style `proxy` parameter to its transports, so commands, PTY,
and filesystem watch traffic follow the same proxy as the REST API and
file transfers (an earlier revision of this PR could only fall back to
`http_proxy`/`https_proxy` env vars for RPC):

```python
sandbox = Sandbox.create(proxy="http://user:pass@localhost:8030")
# REST *and* RPC (commands, PTY, watch) traffic goes through the proxy
result = sandbox.commands.run("echo through-the-proxy")
```

Notes:
- `e2b_connect` is no longer shipped in the wheel; code importing it
directly should switch to `connectrpc` (`ConnectError`, `Code`) — SDK
exception types are unchanged.
- The generated `e2b.envd.*.*_pb2` modules are replaced by `protobuf-py`
equivalents (`process_pb`, `filesystem_pb`) with a different message API
(`Oneof` objects, `has_field`); these are internal modules —
`e2b-code-interpreter` and `e2b-desktop` were verified not to import
them.
- RPC transports are cached per proxy URL. `httpx.URL` and `httpx.Proxy`
proxies keep working for RPC calls when they reduce to a proxy URL
(`httpx.Proxy` auth is folded back into the URL userinfo); `httpx.Proxy`
extras that pyqwest can't express — custom headers, an `ssl_context` —
raise `InvalidArgumentException` rather than being silently dropped.
- Plain (non-Connect-encoded) HTTP error responses — an edge proxy or
gateway answering for envd — keep the vendored client's status mapping
even when they carry a JSON body that isn't a valid Connect error (e.g.
a gateway's `{"code": 429}` raises `RateLimitException`, not a
misleading sandbox-timeout); only JSON bodies with a valid Connect
`code` string are left to connectrpc to parse. An envd response that
fails to decode surfaces as a `SandboxException` with a clear message —
the SDK's JSON codec raises a typed `ConnectError(INTERNAL)` at the
source (connectrpc re-raises codec-raised `ConnectError`s unchanged),
rather than the error being reconstructed from `__cause__` heuristics in
the exception mapper.
- pyqwest 0.7.0 explicit transports default to an **empty TLS root
store** (0.6.2 used reqwest's defaults), so the envd transports pass
`tls_include_system_certs=True`; the dependency floor is
`pyqwest>=0.7.0` accordingly.
- Connection retries (`E2B_CONNECTION_RETRIES`, default 3) use pyqwest's
transport-level retry middleware (`pyqwest.middleware.retry`), narrowed
to retry only the builtin `ConnectionError` — raised solely while
establishing the connection, before the request could have reached envd
— with exponential backoff. A retry can therefore never replay a
delivered request, for unary and streaming RPCs alike; the previous
stack's replay of unary calls whose connection dropped mid-request is
dropped deliberately, since it could re-execute a delivered call (e.g.
`SendInput`). Pinned by unit tests plus end-to-end tests driving the
generated stubs through the middleware
(`tests/test_envd_retry_transport.py`).
- For async streaming calls (`commands.run`/`connect`, PTY,
`watch_dir`), `request_timeout` bounds opening the stream — the wait
until envd confirms with a start event, matching the JS SDK's
`requestTimeoutMs` — raising `TimeoutException` and cancelling the
HTTP/2 stream when exceeded (pinned frame-level in
`tests/test_envd_stream_reset.py`). The running stream stays bounded by
the command/watch `timeout`. The sync SDK cannot interrupt its blocking
wait, so `request_timeout` is not applied to sync stream setup — both
setup and the running stream are bounded by `timeout` (unlimited when
`0`).
- The RPC logging interceptor was upstreamed to pyqwest as a logging
middleware
([curioswitch/pyqwest#192](https://github.com/curioswitch/pyqwest/pull/192));
the SDK keeps its own `LoggingInterceptor` until that merges and ships
in a release the SDK can depend on.
- `pyqwest` ships binary wheels for manylinux/musllinux (x86_64,
aarch64), macOS arm64 + x86_64 (Intel wheels landed in 0.7.0), Windows
x64, and PyPy.
- The `RST_STREAM`-on-early-close behavior is pinned by frame-level
regression tests (`tests/test_envd_stream_reset.py`): a plaintext HTTP/2
server records the frames the real generated clients (with the SDK's
codec and interceptors) send — early close via `disconnect()`, close
through the logging interceptor, and abandoning the stream must all send
`RST_STREAM(CANCEL)`; normal completion must send none (sync + async).
- `E2B_MAX_CONNECTIONS` no longer applies to sandbox RPC traffic:
reqwest's pool bounds only idle connections per host
(`E2B_KEEPALIVE_EXPIRY`, `E2B_MAX_KEEPALIVE_CONNECTIONS`), not the total
number of open connections. It still applies to the REST API and file
transfers.
- The sync sandbox modules build one RPC client each and share it across
threads — the connectrpc sync client is stateless per call over the
process-global transport (verified with a 16-thread frame-level test);
only the httpx envd API clients stay per-thread with their transports.
- Also fixes numeric env-var parsing (`E2B_KEEPALIVE_EXPIRY`,
`E2B_MAX_KEEPALIVE_CONNECTIONS`, `E2B_MAX_CONNECTIONS`,
`E2B_CONNECTION_RETRIES`): an empty-string value now falls back to the
default instead of raising `ValueError` at import time.
2026-07-24 05:41:04 -07: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 761ee5ebf9 docs: instruct linking Linear issues when opening PRs (#1604)
## Description

Adds a rule to `CLAUDE.md` instructing agents to use the Linear MCP (if
available) when opening a new pull request — either linking to related
existing issues or creating a new issue from the PR description.

Linked issue:
[SDK-267](https://linear.app/e2b/issue/SDK-267/claudemd-require-linear-issue-linking-when-opening-prs)

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

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 16:26:38 -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 e334c87f8f ci(js-sdk): split test workflow into parallel per-runtime jobs (#1588)
Splits the JS SDK test workflow's serial ubuntu job (Node → Cloudflare
pool → Cloudflare deploy → Bun → Deno) into a `fail-fast: false` matrix
of parallel legs: `node` on ubuntu and windows, plus `bun`, `deno`,
`cloudflare`, and `cloudflare-deploy` on ubuntu. This cuts wall-clock
time to the slowest single suite and lets a failed runtime be identified
and re-run individually; Playwright setup is gated to the `node` legs
(the only ones running the vitest browser project), while every leg
keeps `pnpm build` since the unit bundle test and both Cloudflare
configs require `dist/` in CI. A new `node-only` workflow input
collapses the matrix to the two Node legs, and the staging caller in
`sdk_tests.yml` sets it — Bun/Deno only run API-free unit suites and the
Cloudflare legs just add sandbox load, so the extra runtimes are
exercised against production only. The `workflow_call` interface stays
backward-compatible, so `release.yml`, `release-candidate.yml`, and the
required `SDK Tests / SDK Tests Status` check need no changes and keep
the full matrix. Production coverage is identical to before — the
Windows job never ran the extra suites anyway.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 12:43:56 +00:00
Mish Ushakov 2defe39bd7 feat(cli): rename team to project in ~/.e2b/config.json (#1570)
Bumps `~/.e2b/config.json` to `version: 2` and renames
`teamName`/`teamId`/`teamApiKey` to
`projectName`/`projectId`/`projectApiKey`. The rename is internal to the
config file format — all user-facing CLI output, flags (`--team`), and
env vars (`E2B_TEAM_ID`) still say "team", and API `teamID` parameters
are unchanged.

Existing v1 configs keep working: they are converted to the new format
in memory on read, and the file on disk is left untouched — the v2
format is only persisted through paths that write the config anyway
(login, `e2b auth configure`, token refresh), so older CLI versions can
still read the file in the meantime. Unrecognized configs are no longer
deleted either; the CLI treats them as signed out and `e2b auth login`
overwrites them. Tools that read the config file directly must handle
the new field names once the file is written in the v2 format.

## Usage

```jsonc
// ~/.e2b/config.json (fresh login, or any config write after upgrading)
{
  "version": 2,
  "projectName": "default",
  "projectId": "team-id",
  "projectApiKey": "e2b_...",
  // identity, oauth, tokens, last_refresh unchanged
}
```

CLI output is unchanged:

```bash
$ e2b auth info
You are logged in as user@example.com,
Selected team: default (team-id)
```

## Testing

`user_config_migration.test.ts` covers in-memory v1→v2 migration, v2
pass-through, and unrecognized configs being treated as signed out
without deleting the file; existing config-permissions and backend
integration tests updated to the new fields. `format`, `lint`,
`typecheck`, `build`, and `pnpm run test` pass (backend integration
suites are environment-gated on credentials).

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
e2b@2.35.3 @e2b/cli@2.15.0
2026-07-23 14:22:31 +02: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 a16dcdfc0c feat(cli): rename --team flag to --project and add E2B_PROJECT_ID env var (#1580)
Renames the `--team` flag to `-t, --project` on `template list`,
`template publish`, `template unpublish`, and `template delete`.
`--team` keeps working as a hidden alias that prints a deprecation
warning to stderr. The project ID can now also be set via the new
`E2B_PROJECT_ID` environment variable, with `E2B_TEAM_ID` still
supported as a fallback. Resolution precedence: `--project` > `--team` >
`E2B_PROJECT_ID` > `E2B_TEAM_ID` > `~/.e2b/config.json`.

## Usage

```sh
e2b template list --project <project-id>   # new flag (also -t)
e2b template list --team <project-id>      # still works, warns: "The --team flag is deprecated, use --project instead."

E2B_PROJECT_ID=<project-id> e2b template list   # new env var
E2B_TEAM_ID=<project-id> e2b template list      # still supported
```

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@e2b/cli@2.14.0
2026-07-23 10:51:25 +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>
e2b@2.35.2
2026-07-23 10:42:58 +00:00
Jakub Novák 0ad6c212cf chore: change codeowners (#1582) 2026-07-23 02:41:04 -07: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) @e2b/cli@2.13.4 e2b@2.35.1 2026-07-22 10:36:04 -07:00
Mish Ushakov 04827ab163 chore(cli): remove dead e2b.toml write path (#1569)
## Description

The CLI no longer writes `e2b.toml` anywhere, so this removes the dead
code around it:

- `saveConfig` and its `getConfigHeader` helper in
`packages/cli/src/config/index.ts` had zero callers — removed along with
now-unused imports.
- The `team_id` field is dropped from the config schema and the unused
`localConfigTeamId` parameter from `resolveTeamId` — nothing consumed it
since the legacy `template build` command was removed. Team resolution
is now: `--team` flag → `E2B_TEAM_ID` env → `~/.e2b/config.json` (the
last only when `E2B_API_KEY` isn't set). yup ignores unknown keys, so
legacy tomls containing `team_id` still parse.

Parsing (`loadConfig`, `deleteConfig`, `getConfigPath`) is intentionally
kept as the backward-compatibility read path for legacy projects:
`template migrate` (its whole purpose), `template publish`, `template
delete`, and `sandbox create`. No user-facing behavior changes; includes
a `@e2b/cli` patch changeset.

## Test

Format, lint, and typecheck pass; CLI tests: 88 passed, 8 skipped (one
pre-existing backend integration suite fails only due to missing
`E2B_API_KEY` in the environment).

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 17:06:07 +02:00
Jakub Kracina c0fe6081bd feat(cli): rename user-visible "team" wording to "project" in terminal output (#1577)
## Summary

Copy-only rename of the remaining user-visible "team" strings to
"project" in the CLI (EN-1891) — part of the Teams → Projects rename,
following the dashboard copy pass. 12 string literals across `auth
login`, `auth info`, `auth configure`, and `template publish`; no flag,
env var, config key, API call, or exit-code behavior changes.

Explicitly untouched (owned by other PRs): `--team` flag + help text
(#1571), `~/.e2b/config.json` keys (#1570), `e2b.toml` `team_id`
(#1569), internal identifiers and API `Team` types. Best merged after
#1569–#1571 to keep their rebases trivial.

## Usage examples

```
$ e2b auth login
Logged in as you@e2b.dev with selected project Your Project

$ e2b auth info
You are logged in as you@e2b.dev,
Selected project: Your Project (a1b2c3d4)

$ e2b auth configure
? Select project
  Your Project (a1b2c3d4) (currently selected project)
Project Your Project (a1b2c3d4) selected.

$ e2b template publish
⚠️ This will make the template public to everyone outside your project
```

## Testing

- No new tests — strings only, not functionality (per review). Existing
suite passes except the pre-existing backend-integration suites that
need live sandbox access (fail identically on main).
- Patch changeset included.
2026-07-22 16:33:08 +02:00
Mish Ushakov 4990471484 fix(cli): sort sandbox list by timestamp instead of locale date string (#1573)
Fixes #1572

## Problem

`e2b sandbox list` sorted rows *after* converting `startedAt` to a
locale string, so ordering was lexicographic over strings like
`"9/1/2026, 10:00:00 AM"`. In en-US, `"9/..."` sorts after `"10/..."`,
so September sandboxes appeared after October ones — chronological order
broke at any single-digit/double-digit month or day boundary.

## Fix

Sort by the raw `startedAt` timestamp (with the existing sandbox-ID
tiebreak) before formatting for display. The row-building logic is
extracted into an exported `buildTableRows` helper and covered by unit
tests, including the September/October regression case. The input array
is no longer mutated in place.

## Example

```
$ e2b sandbox list --state paused

Paused sandboxes
Sandbox ID   ...  Started at
sbx-sep      ...  9/1/2026, 12:00:00 PM    ← previously listed after October
sbx-oct      ...  10/1/2026, 11:00:00 AM
```

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

<!-- codesmith:footer -->
---
<a
href="https://app.blacksmith.sh/e2b-dev/codesmith/E2B/pr/1573"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img
alt="View with Codesmith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"></picture></a>
<a
href="https://backend.blacksmith.sh/track/enable-autofix?expires=1787240223&installation_model_id=14389&pr_number=1573&repository=e2b-dev%2FE2B&return_to=https%3A%2F%2Fgithub.com%2Fe2b-dev%2FE2B%2Fpull%2F1573&signature=85af1311c0e7aa33bbe8ea331f8bb86f82e89ab6e8ec39b29ab776c3a1466cc1"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img
alt="Autofix with Codesmith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a>
<sup>Need help on this PR? Tag <code>/codesmith</code> with what you
need. Autofix is disabled.</sup>

<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 15:17:13 +02:00
Mish Ushakov be1ffa19f6 chore(deps): remove dead pnpm overrides and add CLI changeset (#1561)
Follow-up to #1559 with two changes. First, it removes four
`pnpm.overrides` entries whose targets are no longer in the dependency
graph at all — `@next/eslint-plugin-next>glob` (the parent package is
gone), `yaml@2.x`, `@tootallnate/once`, and `flatted`; the lockfile
change is header-only and no resolved package versions change, verified
with a clean `pnpm audit`. The remaining overrides are kept because no
parent's declared range excludes the vulnerable versions, so they are
the only enforcement of the patched floors. Second, it adds a patch
changeset for `@e2b/cli`: the CLI bundles all runtime dependencies into
`dist/index.js` at build time (tsdown `alwaysBundle`), so the patched
transitive deps from #1559 (e.g. brace-expansion 5.0.7 via the
glob/minimatch chains) only reach users through a new release. No
changeset is needed for the `e2b` SDK or Python SDK since they publish
dependency ranges that resolve fresh at user install time. Supersedes
#1560.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 11:20:05 +00:00
Mish Ushakov f6cb5a0da7 fix(deps): resolve open Dependabot alerts via pnpm overrides (#1559)
Fixes all 6 open [Dependabot
alerts](https://github.com/e2b-dev/E2B/security/dependabot) plus 2
advisories surfaced by `pnpm audit`, by bumping vulnerable transitive
dependencies through `pnpm.overrides`: vite 6.4.2→6.4.3
(`server.fs.deny` bypass, NTLMv2 hash disclosure), js-yaml 3.14.2→3.15.0
/ 4.1.1→4.3.0 (merge-key DoS), @babel/core 7.27.1→7.29.7 (arbitrary file
read via `sourceMappingURL`), brace-expansion 1.1.12→1.1.16 /
5.0.5→5.0.7 (DoS), and underscore 1.13.6→1.13.8 (recursion DoS). Vite
required a manual lockfile version+integrity rewrite because pnpm does
not re-resolve auto-installed optional peers (vite enters the graph via
vitest) when an override changes. Only brace-expansion@5 is in a runtime
dependency chain (`glob` in the SDK/CLI); all other bumps are dev
tooling, no package manifests changed, so no changeset is needed.
Verified with a clean `pnpm audit`, passing
lint/typecheck/format/builds, and a live vitest smoke test against a
real sandbox.

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

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 12:53:54 +02:00
Mish Ushakov 36639f5321 feat(cli): remove per-command tag from integration attribution (#1557)
## Description

Removes the `e2b-cli-command/<command>` token (added in #1544) from the
CLI's User-Agent integration attribution, so CLI traffic is attributed
only by tool and version. This also lets `connectionConfig` and `client`
in `packages/cli/src/api.ts` go back to plain `const` exports, deleting
the per-command config/client rebuild machinery and the `preAction` hook
that drove it. The attribution test now only checks the SDK and CLI
tags, and a patch changeset for `@e2b/cli` is included.

User-Agent sent by `e2b sandbox list`, before and after:

```
before: e2b-js-sdk/2.9.0 (Node.js/22.11.0) e2b-cli/2.13.3 e2b-cli-command/sandbox.list
after:  e2b-js-sdk/2.9.0 (Node.js/22.11.0) e2b-cli/2.13.3
```

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:57:04 +00: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>
e2b@2.35.0 @e2b/python-sdk@2.34.0
2026-07-17 09:50:34 +00:00
Mish Ushakov e68b876689 fix(ci): notify success on releases that skip CLI tests (#1484)
## What

`report-success` (the **Release Succeeded** Slack notification) silently
skips on releases that don't bump the CLI — even when the release
publishes successfully.

## Why

The job used:

```yaml
report-success:
  needs: [preflight, publish]
  if: needs.publish.result == 'success'
```

That `if` contains no status-check function (`always()`, `!cancelled()`,
`failure()`, `success()`). When `cli-tests` is skipped — which happens
whenever the changeset releases the SDKs but not the CLI (`cli-tests`
has `if: needs.preflight.outputs.cli == 'true'`) — GitHub Actions **skip
propagation** cascades through the dependency graph and skips
`report-success` too, before its condition is meaningfully evaluated. So
no success notification fires.

The `publish` job avoids this exact trap because its `if` already starts
with `(!cancelled())`, which is why `publish` runs (and succeeds)
regardless. `report-success` just lacked the same guard.

### Evidence

`report-success` skipped **iff** `cli-tests` skipped, across recent
releases:

| Run | `cli-tests` | `report-success` |
|-----|-------------|------------------|
| [28189674867](https://github.com/e2b-dev/E2B/actions/runs/28189674867)
| skipped | **skipped**  |
| 27978450216 | skipped | **skipped**  |
| 28150204186 | ran  | fired  |
| 27843301597 | ran  | fired  |

## Fix

```diff
 report-success:
   needs: [preflight, publish]
-  if: needs.publish.result == 'success'
+  if: (!cancelled()) && needs.publish.result == 'success'
```

`(!cancelled())` disables skip propagation so the condition is always
evaluated, while `needs.publish.result == 'success'` preserves the
original intent: notify only when the publish actually succeeded.

`report-failure` (`if: failure()`) and `report-start` are unaffected —
both already evaluate correctly.

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

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 09:46:32 +00:00