Skip to content

feat: move the Code Interpreter template and e2b-charts into the monorepo - #1961

Draft
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1791475809-move-code-interpreter-template
Draft

devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1791475809-move-code-interpreter-template

Conversation

@devin-ai-integration

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

Copy link
Copy Markdown
Contributor

Summary

Moves the Code Interpreter sandbox template (code-interpreter-v1), its HTTP test suite and the e2b-charts package from e2b-dev/code-interpreter (@2dd4042) into this repo, along with their CI/CD. As in #1768, the files are byte-identical copies apart from the intentional edits listed below.

From (code-interpreter) To pnpm name
template/ templates/code-interpreter/ @e2b/code-interpreter-template (unchanged)
tests/ templates/code-interpreter/tests/ (still its own uv project on PyPI e2b) — (separate workspace package dropped)
chart_data_extractor/ packages/charts-python/ @e2b/data-extractor → @e2b/charts-python (PyPI e2b-charts unchanged)

pnpm-workspace.yaml now includes templates/*, so the template stays a versioned Changesets package. templates/base and templates/httpbin have no package.json, so pnpm ignores them. The unreleased .changeset/jupyter-unix-socket.md is moved over as well.

Intentional edits:

  • READMEs: paths updated.
  • Old root Makefile targets: moved to templates/code-interpreter/Makefile.
  • Template lint/format: now uvx ruff@0.11.12, because bare ruff isn't on PATH in Lint CI.
  • Charts package.json: follows the code-interpreter-python scripts (postVersion --frozen, lock, postPublish via trusted publishing). The unused eslint devDeps are dropped, and test no longer passes -n 4 (pytest-xdist isn't a dependency).
  • Charts pyproject.toml: URLs point at this repo.

CI/CD

  • code_interpreter_template_tests.yml (reusable): build (build_ci.py, pins e2b_charts==<charts-python version>) → test (pytest in tests/) → cleanup (deletes the CI template). It merges the old build_test_template, code_interpreter_tests and cleanup_build_template workflows.
  • charts_python_tests.yml (reusable): the old charts_tests.yml.
  • code_interpreter_template_build.yml: manual dispatch (foxtrot/staging/juliett, skip_cache, push_docker_image) and workflow_call. It replaces build_prod_template.yml and the release job that built the Docker image.
  • sdk_tests.yml: new path-filtered production jobs, also added to status. The template filter leaves out *shared on purpose, so lockfile bumps don't build a template.
  • release.yml:
    • Changesets for either package select their tests before publish.
    • e2b-charts is published by pnpm run -r postPublish.
    • A new code-interpreter-template-build job runs after publish and builds foxtrot + Docker Hub from publish.outputs.released_sha. That output is new in publish_packages.yml, and building from it means the template pins the newly released charts version.
    • Behavior change: the prod template is now built after tagging, where it used to be before. If it fails, re-run the job; the template version is only a changelog entry.

Setup needed before the first release

  • Add the repo secrets E2B_PROD_API_KEY, E2B_STAGING_API_KEY, E2B_JULIETT_API_KEY and the var E2B_DOMAIN, using the same values as e2b-dev/code-interpreter. DOCKERHUB_* already exist here.
  • Add a PyPI trusted publisher for e2b-charts (e2b-dev/E2B, release.yml, environment deployment) to replace CHARTS_PYPI_TOKEN.

The source removal in e2b-dev/code-interpreter is e2b-dev/code-interpreter#348; merge it after this PR.

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

…repo

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

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

changeset-bot Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: dda9349

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

This PR includes changesets to release 1 package
Name Type
@e2b/code-interpreter-template 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

Comment thread templates/code-interpreter/server/main.py Fixed
Comment thread templates/code-interpreter/server/main.py Fixed
@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Package Artifacts

Built from f1c5194. Download artifacts from this workflow run.

JS SDK (e2b@2.54.1-devin-1791475809-move-code-interpreter-template.0, with @e2b/dockerfile-utils@0.1.1-devin-1791475809-move-code-interpreter-template.0):

npm install ./e2b-dockerfile-utils-0.1.1-devin-1791475809-move-code-interpreter-template.0.tgz ./e2b-2.54.1-devin-1791475809-move-code-interpreter-template.0.tgz

CLI (@e2b/cli@2.21.2-devin-1791475809-move-code-interpreter-template.0):

npm install ./e2b-cli-2.21.2-devin-1791475809-move-code-interpreter-template.0.tgz

Code Interpreter JS SDK (@e2b/code-interpreter@2.8.3-devin-1791475809-move-code-interpreter-template.0):

npm install ./e2b-code-interpreter-2.8.3-devin-1791475809-move-code-interpreter-template.0.tgz

Desktop JS SDK (@e2b/desktop@2.4.1-devin-1791475809-move-code-interpreter-template.0):

npm install ./e2b-desktop-2.4.1-devin-1791475809-move-code-interpreter-template.0.tgz

Python SDK (e2b==2.54.0+devin.1791475809.move.code.interpreter.template, with e2b-dockerfile-utils==0.1.0+devin.1791475809.move.code.interpreter.template):

pip install ./e2b_dockerfile_utils-0.1.0+devin.1791475809.move.code.interpreter.template-py3-none-any.whl ./e2b-2.54.0+devin.1791475809.move.code.interpreter.template-py3-none-any.whl

Code Interpreter Python SDK (e2b-code-interpreter==2.10.3+devin.1791475809.move.code.interpreter.template):

pip install ./e2b_code_interpreter-2.10.3+devin.1791475809.move.code.interpreter.template-py3-none-any.whl

Desktop Python SDK (e2b-desktop==2.6.1+devin.1791475809.move.code.interpreter.template):

pip install ./e2b_desktop-2.6.1+devin.1791475809.move.code.interpreter.template-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.

Reviewed against TASTE.md (e2b-dev/sdk-harness).

Scope: only the code this PR adds. Most of TASTE.md governs the public surface of packages/js-sdk and packages/python-sdk, which this PR doesn't touch. So the rules that could apply were the ones covering code the SDKs run or call into: command-string composition (T-43), env-var config (T-49/T-50), HTTP failure detection (T-61), named defaults and enums (T-15/T-47), errors (T-58/T-62/T-63), and use of the current SDK surface in the build scripts (T-3/T-66). The ported e2b_charts package (ChartType enum, pydantic models, flat __init__ exports) raised nothing worth flagging under T-15/T-16/T-54.

6 violations, all commented inline:

  • T-43 ×2: server/messaging.py puts user-supplied envs and cwd into kernel code without quoting.
  • T-50 ×2: server/envs.py (E2B_LOCAL=false turns local mode on) and build_debug.py (empty var gives an empty alias).
  • T-61 ×1: server/envs.py returns envd's error body as the env-var map.
  • T-3/T-66 ×1: the build scripts call Template.build with the deprecated alias= kwarg.

Same issues on lines not commented separately:

  • T-3/T-66: build_ci.py L7, build_debug.py L17 and build_test.py L9 also pass alias=. Move the name to the second positional argument.
  • T-43: _delete_env_var_snippet in server/messaging.py (L171-180) needs the same quoting as the setter.

Minor, not tied to a specific line:

  • T-15/T-47: the server dispatches on bare language literals ("python", "javascript", "typescript", …) in contexts.py and messaging.py, and repeats "/home/user" and port 49999 (also hard-coded in template.py and tests/conftest.py). A Language enum and named constants would put each of these in one place.
  • T-58/T-63: contexts.create_context raises a bare Exception when session creation fails. On a cwd failure it also returns a PlainTextResponse from a function annotated -> Context, so post_execute then fails on context.id. Raise a specific error and map it to the HTTP response in main.py.

Written by Devin

Comment on lines +157 to +166
if self.language == "python":
return f"import os; os.environ['{key}'] = '{value}'"
elif self.language in ["javascript", "typescript"]:
return f"process.env['{key}'] = '{value}'"
elif self.language == "r":
return f'Sys.setenv({key} = "{value}")'
elif self.language == "java":
return f'System.setProperty("{key}", "{value}");'
elif self.language == "bash":
return f"export {key}='{value}'"

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.

T-43: escape anything you interpolate into a command string. key and value come from the user's envs and go into the code unquoted, so a value like it's or C:\path, or a newline, gives you a syntax error or a silently wrong value in every kernel. A JSON string literal is valid Python, JS, R and Java source, and shlex.quote handles bash (this needs import shlex):

Suggested change
if self.language == "python":
return f"import os; os.environ['{key}'] = '{value}'"
elif self.language in ["javascript", "typescript"]:
return f"process.env['{key}'] = '{value}'"
elif self.language == "r":
return f'Sys.setenv({key} = "{value}")'
elif self.language == "java":
return f'System.setProperty("{key}", "{value}");'
elif self.language == "bash":
return f"export {key}='{value}'"
if self.language == "python":
return f"import os; os.environ[{json.dumps(key)}] = {json.dumps(value)}"
elif self.language in ["javascript", "typescript"]:
return f"process.env[{json.dumps(key)}] = {json.dumps(value)}"
elif self.language == "r":
return f"Sys.setenv({json.dumps(key)} = {json.dumps(value)})"
elif self.language == "java":
return f"System.setProperty({json.dumps(key)}, {json.dumps(value)});"
elif self.language == "bash":
return f"export {key}={shlex.quote(value)}"

The matching _delete_env_var_snippet (L171-180) needs the same fix. The bash key stays bare because a variable name can't be quoted, so check it against ^[A-Za-z_][A-Za-z0-9_]*$ first.

Comment on lines +298 to +312
if language == "python":
request = self._get_execute_request(message_id, f"%cd {path}", True)
elif language in ("javascript", "typescript"):
request = self._get_execute_request(
message_id, f"process.chdir('{path}')", True
)
elif language == "r":
request = self._get_execute_request(message_id, f"setwd('{path}')", True)
# This does not actually change the working directory, but sets the user.dir property
elif language == "java":
request = self._get_execute_request(
message_id, f'System.setProperty("user.dir", "{path}");', True
)
elif language == "bash":
request = self._get_execute_request(message_id, f"cd '{path}'", True)

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.

T-43: same problem for cwd. It comes from the user (CreateContext.cwd) and is interpolated raw, so a path with a space breaks %cd and a path with a ' breaks JS, R and bash. Quote it per language:

Suggested change
if language == "python":
request = self._get_execute_request(message_id, f"%cd {path}", True)
elif language in ("javascript", "typescript"):
request = self._get_execute_request(
message_id, f"process.chdir('{path}')", True
)
elif language == "r":
request = self._get_execute_request(message_id, f"setwd('{path}')", True)
# This does not actually change the working directory, but sets the user.dir property
elif language == "java":
request = self._get_execute_request(
message_id, f'System.setProperty("user.dir", "{path}");', True
)
elif language == "bash":
request = self._get_execute_request(message_id, f"cd '{path}'", True)
if language == "python":
request = self._get_execute_request(
message_id, f"import os; os.chdir({json.dumps(path)})", True
)
elif language in ("javascript", "typescript"):
request = self._get_execute_request(
message_id, f"process.chdir({json.dumps(path)})", True
)
elif language == "r":
request = self._get_execute_request(
message_id, f"setwd({json.dumps(path)})", True
)
# This does not actually change the working directory, but sets the user.dir property
elif language == "java":
request = self._get_execute_request(
message_id, f"System.setProperty(\"user.dir\", {json.dumps(path)});", True
)
elif language == "bash":
request = self._get_execute_request(
message_id, f"cd {shlex.quote(path)}", True
)


import httpx

LOCAL = os.getenv("E2B_LOCAL", False)

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.

T-50: an empty env var means unset, and a flag-shaped var should be parsed with a comparison. Here the raw string is used for truthiness, so E2B_LOCAL=false or E2B_LOCAL=0 turns local mode on and get_envs returns the stub instead of the real envd envs. Compare against an explicit value, the way tests/conftest.py::is_debug already does:

Suggested change
LOCAL = os.getenv("E2B_LOCAL", False)
LOCAL = os.getenv("E2B_LOCAL", "false").lower() == "true"

Comment on lines +17 to +20
response = await client.get(
f"http://localhost:{ENVD_PORT}/envs", headers=headers
)
return response.json()

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.

T-61: detect failure from the HTTP status. When envd answers 401 or 5xx, its error body is parsed and returned as the env-var map. That map then feeds the per-language _set_env_vars_code, so instead of a clear error you get garbage variables. Check the status before you trust the body:

Suggested change
response = await client.get(
f"http://localhost:{ENVD_PORT}/envs", headers=headers
)
return response.json()
response = await client.get(
f"http://localhost:{ENVD_PORT}/envs", headers=headers
)
response.raise_for_status()
return response.json()


load_dotenv()

alias = os.getenv("E2B_DEBUG_TEMPLATE", "code-interpreter-debug")

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.

T-50: an empty env var means unset. With the two-argument os.getenv, an exported but empty E2B_DEBUG_TEMPLATE= (for example from a copied .env) gives alias == '', and the build fails with an unhelpful name error instead of using the default. Use the or form:

Suggested change
alias = os.getenv("E2B_DEBUG_TEMPLATE", "code-interpreter-debug")
alias = os.getenv("E2B_DEBUG_TEMPLATE") or "code-interpreter-debug"

Comment on lines +12 to +13
make_template(),
alias="code-interpreter-v1",

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.

T-3 (required arguments are positional) / T-66 (deprecated surface points to the migration path): the template name is a required argument of Template.build(template, name, ...), and the alias= kwarg is deprecated ("(Deprecated) Alias name for the template. Use name instead." in e2b/template_sync/main.py). Template code that ships inside the SDK monorepo should show the current form, not the deprecated one:

Suggested change
make_template(),
alias="code-interpreter-v1",
make_template(),
"code-interpreter-v1",

build_ci.py, build_debug.py and build_test.py need the same change (see the review summary).

…reter

Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
client, websockets, language, "/home/user"
)
except Exception as e:
return PlainTextResponse(str(e), status_code=500)
try:
return await create_context(client, websockets, language, cwd)
except Exception as e:
return PlainTextResponse(str(e), status_code=500)
@linear-code

linear-code Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

SDK-390

SDK-389

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.

2 participants