Skip to content

fix(js-sdk): send envd file uploads over HTTP/1.1, retire poisoned HTTP/2 dispatchers - #2086

Merged
mishushakov merged 2 commits into
mainfrom
devin/1793727850-envd-uploads-http1
Nov 3, 2026
Merged

mishushakov merged 2 commits into
mainfrom
devin/1793727850-envd-uploads-http1

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes SDK-425: concurrent sandbox.files.write() calls fail with TypeError: fetch failed → NGHTTP2_ENHANCE_YOUR_CALM since e2b ≥ 2.35 (undici 8). Simpler alternative to #2083 (byte budget + one-off sessions), same measurements.

Cause. The RST is sent by Node's own http2 client, not by E2B infra. Node queues each buffered request body in full against the session's 10 MB maxSessionMemory; if a response HEADERS frame arrives on any stream while that budget is exhausted, Node resets that stream with ENHANCE_YOUR_CALM. undici 7 kept one request per session (so our 10-connection pool = 10 budgets); undici 8 multiplexes, so every envd REST request in the process shares one session and one budget — parallel or large uploads reset each other and the reads next to them. Streams destroyed before their body is flushed (timeouts, aborts, killed sandboxes) additionally leak their queued bytes on Node ≤ 24 (fixed on main by nodejs/node#67113), so a session degrades until every small write on it fails — the customer's "climbing since Monday".

Fix (JS SDK only — Python uses pyqwest/hyper, not Node http2):

  1. Filesystem POSTs /files (octet-stream, streamed, gzip and multipart uploads) through createEnvdUploadFetch — an HTTP/1.1 dispatcher pool of its own (E2B_ENVD_UPLOAD_CONNECTIONS, default 50, like E2B_ENVD_RPC_CONNECTIONS); everything else (read, makeDir, RPC, health…) stays on HTTP/2, and httpVersion / E2B_HTTP_VERSION keep governing those. The ConnectionOpts.httpVersion JSDoc documents the exception.
    // sandbox/filesystem/index.ts
    const uploadFetch = createEnvdUploadFetch({ proxy: connectionConfig.proxy })
    …
    this.envdApi.api.POST('/files', { …, fetch: this.uploadFetch })
    One request per connection, no shared memory budget — what undici 7 effectively gave 2.27.1 (which capped uploads at 10 per process; this pool allows 50, beyond that uploads queue in undici with their request timeout running).
  2. buildDispatchedFetch (src/undici.ts): a request that fails with ERR_HTTP2_STREAM_ERROR + NGHTTP2_ENHANCE_YOUR_CALM/NGHTTP2_INTERNAL_ERROR (isPoisonedHTTP2SessionError) retires that dispatcher and builds a fresh one for subsequent requests. The failed request is not replayed (envd may already have applied the write). Only the dispatcher that served the failing request can replace the current one. Safety net for whatever else might fill a session.

No API change.

Measured (built package, real sandboxes, Node 24.22; ALPN confirmed: writes http/1.1, reads h2):

scenario 2.62.1 this PR
10 × 2 MB parallel writes, 5 rounds 16/50 failed 0/50
30 × 1 MB, 20 × 1 MB, 4 × 16 MB, customer pattern (makeDir + 10 parallel small writes ×200) — 0 failed
16 MB write + 10 concurrent files.read() 30/30 reads failed 0/33
poisoned session (maxSessionMemory=1, aborted big writes) 50/50 fail, forever 0 failed
streamed (async iterable), gzip, multipart, string, aborted upload — all OK

Upstream: backport request on nodejs/node#67113 (nodejs/node#67113 (comment)) and undici issue nodejs/undici#6149.

Link to Devin session: https://app.devin.ai/sessions/98f09d080013403b954d3f7b07c51b3b
Open in Devin Desktop: https://app.devin.ai/desktop/session/98f09d080013403b954d3f7b07c51b3b?variant=devin
Requested by: @mishushakov

…TP/2 dispatchers

Node's HTTP/2 client charges each queued request body in full against the
session's 10 MB memory budget and resets whichever stream's response headers
arrive while it is exhausted (NGHTTP2_ENHANCE_YOUR_CALM). Since undici 8
every envd REST request of a process multiplexes over one session, so
concurrent or large files.write() uploads reset each other and the reads
next to them (SDK-425).

files.write() now POSTs /files through the HTTP/1.1 envd fetcher — one
request per pooled connection, no shared budget, as undici 7 effectively
gave 2.27.1 — while everything else stays on HTTP/2. A dispatcher whose
session Node reports as out of memory is retired and replaced; the failed
request is not replayed since envd may already have applied it.

Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@changeset-bot

changeset-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8256418

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
e2b Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cla-bot cla-bot Bot added the cla-signed label Nov 3, 2026
@linear-code

linear-code Bot commented Nov 3, 2026

Copy link
Copy Markdown

SDK-425

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Package Artifacts

Built from 7869b88. Download artifacts from this workflow run.

JS SDK (e2b@2.62.2-devin-1793727850-envd-uploads-http1.0, with @e2b/dockerfile-utils@0.1.2-devin-1793727850-envd-uploads-http1.0):

npm install ./e2b-dockerfile-utils-0.1.2-devin-1793727850-envd-uploads-http1.0.tgz ./e2b-2.62.2-devin-1793727850-envd-uploads-http1.0.tgz

CLI (@e2b/cli@2.23.1-devin-1793727850-envd-uploads-http1.0):

npm install ./e2b-cli-2.23.1-devin-1793727850-envd-uploads-http1.0.tgz

Code Interpreter JS SDK (@e2b/code-interpreter@2.9.2-devin-1793727850-envd-uploads-http1.0):

npm install ./e2b-code-interpreter-2.9.2-devin-1793727850-envd-uploads-http1.0.tgz

Desktop JS SDK (@e2b/desktop@2.5.1-devin-1793727850-envd-uploads-http1.0):

npm install ./e2b-desktop-2.5.1-devin-1793727850-envd-uploads-http1.0.tgz

Python SDK (e2b==2.62.0+devin.1793727850.envd.uploads.http1, with e2b-dockerfile-utils==0.1.1+devin.1793727850.envd.uploads.http1):

pip install ./e2b_dockerfile_utils-0.1.1+devin.1793727850.envd.uploads.http1-py3-none-any.whl ./e2b-2.62.0+devin.1793727850.envd.uploads.http1-py3-none-any.whl

Code Interpreter Python SDK (e2b-code-interpreter==2.11.7+devin.1793727850.envd.uploads.http1):

pip install ./e2b_code_interpreter-2.11.7+devin.1793727850.envd.uploads.http1-py3-none-any.whl

Desktop Python SDK (e2b-desktop==2.7.2+devin.1793727850.envd.uploads.http1):

pip install ./e2b_desktop-2.7.2+devin.1793727850.envd.uploads.http1-py3-none-any.whl

devin-ai-integration[bot]

This comment was marked as resolved.

@mishushakov
mishushakov marked this pull request as ready for review November 3, 2026 17:55
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-03T17:58:09.679269Z 7479eb5 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Devin Review: 1 flag

Not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

claude[bot]

This comment was marked as resolved.

Uploads shared the 10-connection envd REST pool, so more than ten concurrent
files.write() calls in a process queued in undici while their request
timeout kept running. They now get a pool of their own (E2B_ENVD_UPLOAD_CONNECTIONS,
default 50). The ConnectionOpts.httpVersion docs state the upload exception.

Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
@mishushakov
mishushakov enabled auto-merge (squash) November 3, 2026 18:25
@mishushakov
mishushakov merged commit 08f05bb into main Nov 3, 2026
77 of 80 checks passed
@mishushakov
mishushakov deleted the devin/1793727850-envd-uploads-http1 branch November 3, 2026 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant