Skip to content

feat(sdk): switch domain from X-E2B-Alt-Used response header - #2001

Draft
devin-ai-integration[bot] wants to merge 6 commits into
mainfrom
devin/1792498715-alt-used-domain
Draft

devin-ai-integration[bot] wants to merge 6 commits into
mainfrom
devin/1792498715-alt-used-domain

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The global API proxy responds with X-E2B-Alt-Used: <full domain> (e.g. e2b-juliett.dev), which names the control plane that owns the API key. The SDKs (JS and sync/async Python) now remember that domain for the rest of the process and send later traffic straight to that plane.

API response → record apiKey → header value             // ApiClient fetch wrapper / httpx response hook
new ConnectionConfig({ apiKey })                         // domain, apiUrl, sandboxUrl unset
  → domain = 'e2b-juliett.dev', apiUrl = 'https://api.e2b-juliett.dev'
  • Process-wide cache, keyed by API key (resolvedDomains.ts / e2b/resolved_domains.py, lock-protected in Python). Every static call builds a new config, so switching only one instance wouldn't carry over to the next call.
  • Gating: discovery (both recording and switching) only applies when debug is off and none of domain, apiUrl or sandboxUrl is set, whether as an option or via E2B_DOMAIN / E2B_API_URL / E2B_SANDBOX_URL. The constructor computes this once as ConnectionConfig.discoverDomain (_discover_domain in Python).
  • Rebuilt configs: configs rebuilt with new ConnectionConfig({ ...config }) pass domain explicitly, so they neither record nor switch. Fresh configs do both. So with an empty cache, the first Sandbox.create keeps the default domain, and later calls pick up the discovered one.
  • Sandbox domain: ConnectionConfig.domain changes, so the sandbox-domain fallback (sandboxDomain ?? domain) follows it. envd traffic then reaches the right plane when the API returns no sandbox domain.
  • Header validation: the value is trimmed and lowercased, then must be a bare hostname (no scheme, port or path). The last valid value wins.
  • Scope: JS records from the final response, after withRetry. envd, the volume-content client and the CLI are unchanged.

Not tested end to end: production doesn't send the header yet. The switching is covered by mocked tests (MSW / httpx.MockTransport). A live create → echo ok → kill smoke test in both SDKs only checks that nothing else broke.

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

devin-ai-integration Bot and others added 2 commits October 20, 2026 12:23
@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

@cla-bot cla-bot Bot added the cla-signed label Oct 20, 2026
@changeset-bot

changeset-bot Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c893d9c

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

This PR includes changesets to release 3 packages
Name Type
e2b Minor
@e2b/python-sdk Minor
@e2b/code-interpreter-python 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

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Package Artifacts

Built from 7eec354. Download artifacts from this workflow run.

JS SDK (e2b@2.58.1-devin-1792498715-alt-used-domain.0, with @e2b/dockerfile-utils@0.1.2-devin-1792498715-alt-used-domain.0):

npm install ./e2b-dockerfile-utils-0.1.2-devin-1792498715-alt-used-domain.0.tgz ./e2b-2.58.1-devin-1792498715-alt-used-domain.0.tgz

CLI (@e2b/cli@2.22.4-devin-1792498715-alt-used-domain.0):

npm install ./e2b-cli-2.22.4-devin-1792498715-alt-used-domain.0.tgz

Code Interpreter JS SDK (@e2b/code-interpreter@2.9.1-devin-1792498715-alt-used-domain.0):

npm install ./e2b-code-interpreter-2.9.1-devin-1792498715-alt-used-domain.0.tgz

Desktop JS SDK (@e2b/desktop@2.5.1-devin-1792498715-alt-used-domain.0):

npm install ./e2b-desktop-2.5.1-devin-1792498715-alt-used-domain.0.tgz

Python SDK (e2b==2.58.0+devin.1792498715.alt.used.domain, with e2b-dockerfile-utils==0.1.1+devin.1792498715.alt.used.domain):

pip install ./e2b_dockerfile_utils-0.1.1+devin.1792498715.alt.used.domain-py3-none-any.whl ./e2b-2.58.0+devin.1792498715.alt.used.domain-py3-none-any.whl

Code Interpreter Python SDK (e2b-code-interpreter==2.11.2+devin.1792498715.alt.used.domain):

pip install ./e2b_code_interpreter-2.11.2+devin.1792498715.alt.used.domain-py3-none-any.whl

Desktop Python SDK (e2b-desktop==2.7.2+devin.1792498715.alt.used.domain):

pip install ./e2b_desktop-2.7.2+devin.1792498715.alt.used.domain-py3-none-any.whl

@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.

TASTE.md review of #2001 (X-E2B-Alt-Used domain switching).

Checked: parity JS / sync / async Python (T-1, T-2), configuration precedence & immutability (T-49, T-50, T-51), client-side validation scope (T-52), debug mode (T-53), entry-point/exports (T-54), naming & named constants (T-12, T-47), null/undefined at the boundary (T-20), docs (T-69).

Violations: 1 rule (T-49), flagged inline in both JS and Python. Parity is otherwise good: Python retries run in the transport under httpx, so the response hook sees the final response just like the JS wrapper after withRetry; the async client gets its own async hook; debug mode is respected; the hostname check on the header is a legitimate footgun guard under T-52; the new modules stay internal.

Not tied to a changed line:

  • T-69: the public domain option docs (JS ConnectionOpts.domain, @default E2B_DOMAIN // environment variable or e2b.app; Python ApiParams.domain) still describe the old resolution. Whatever precedence this lands with, the docstrings should say the effective domain/apiUrl may be replaced by the control-plane domain the API reports, and when that does not happen (custom apiUrl/E2B_API_URL, debug).
  • Outside TASTE: the PR description says proxies never record or switch, but usesDomainApiUrl / uses_domain_api_url only look at domain, apiUrl and debug. With proxy set and the default apiUrl, both recording and switching still happen. Either the code or the description should change.

Comment thread packages/js-sdk/src/connectionConfig.ts Resolved

@mishushakov mishushakov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we should always use E2B_DOMAIN, E2B_API_URL, E2B_SANDBOX_URL, E2B_VOLUME_API_URL

devin-ai-integration Bot and others added 2 commits October 22, 2026 13:51
Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@mishushakov Done in 8348a79. When any of E2B_DOMAIN, E2B_API_URL, E2B_SANDBOX_URL or E2B_VOLUME_API_URL is set, discovery is off entirely: the header isn't recorded and nothing switches. This applies in JS and in sync/async Python, with tests for each var.

Explicit options (domain, apiUrl) are still eligible when the API URL is https://api.<domain>, because SDK resources rebuild configs from resolved ones and pass them explicitly. Let me know if you want an explicit domain / sandboxUrl option to turn it off too.

Written by Devin

@mishushakov

Copy link
Copy Markdown
Member

you don't need to check the particular env vars, just check whether domain, apiUrl, sandboxUrl are specified

devin-ai-integration Bot and others added 2 commits October 22, 2026 14:08
…dboxUrl

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

Copy link
Copy Markdown
Contributor Author

@mishushakov Done in c893d9c (plus 81e4abd, a docstring nit). The env-var list is gone. Discovery now runs only when domain, apiUrl and sandboxUrl are all unspecified, as options or through their usual env fallbacks, and debug is off. This holds in JS and in sync/async Python.

E2B_VOLUME_API_URL no longer matters. The cache is keyed by API key only, since the original domain is now always the default.

One trade-off: configs rebuilt with { ...config } pass domain explicitly, so they don't switch. With an empty cache, the first Sandbox.create stays on the default domain, and calls after it use the discovered one.

Written by Devin

This branch has not been deployed

No deployments
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