Skip to content

fix(python-sdk): don't crash mapping a non-object JSON error body - #1880

Open
winklemad wants to merge 2 commits into
e2b-dev:mainfrom
winklemad:fix-python-sdk-non-object-error-body
Open

winklemad wants to merge 2 commits into
e2b-dev:mainfrom
winklemad:fix-python-sdk-non-object-error-body

Conversation

@winklemad

Copy link
Copy Markdown

The bug

Both Python error mappers assume a JSON error body is an object, so a body that is valid JSON but not an object crashes the mapper instead of producing a SandboxException:

  • e2b/api/__init__.py (handle_api_exception): body["message"] if "message" in body else None raises TypeError: argument of type 'NoneType'/'int' is not iterable when the body is null, a number, or a boolean.
  • e2b/envd/api.py (get_message): e.json().get("message", e.text) raises AttributeError: '…' object has no attribute 'get' when the body is any non-dict (a JSON string, array, or scalar). The surrounding except json.JSONDecodeError does not catch it.
# both crash today, on the real error path:
handle_api_exception(SimpleNamespace(status_code=500, content=b"null", headers={}))
#   -> TypeError: argument of type 'NoneType' is not iterable
handle_envd_api_exception(httpx.Response(502, content=b'"upstream connect error"',
                                         headers={"content-type": "application/json"}))
#   -> AttributeError: 'str' object has no attribute 'get'

Why it matters

Every control-plane call (Sandbox.create/kill/set_timeout/get_info/get_metrics, list().next_items(), and async equivalents) routes errors through site 1; every sandbox.files.read/write/list routes through site 2. The trigger is realistic: a reverse proxy, gateway, or CDN in front of the control plane or envd can return an error with Content-Type: application/json and a bare string or scalar body (e.g. "upstream connect error", null) — common during an upstream outage. When that happens, instead of a clear SandboxException/TimeoutException with the HTTP status, the developer gets an opaque TypeError/AttributeError from deep inside the SDK, masking the actual failure.

This is a JS↔Python divergence: the JS SDK deliberately tolerates non-object bodies — handleApiError uses response.error?.message ?? response.error, and handleEnvdApiError uses typeof res.error === 'string' ? res.error : res.error?.message, with ApiError typed { message?: string } | string. The Python mirror was written assuming a dict.

Fix

Guard both sites for a non-object body and fall back the same way the JS SDK does (use a string body as the message; otherwise fall back to the raw text / None), so the HTTP error still maps to a SandboxException.

Tests / validation

Added regression tests to tests/test_api_exception.py (non-object bodies null/42/true → SandboxException with the status) and tests/test_envd_api_exception.py (get_message on a JSON string and array body; a non-dict envd error mapping without crashing). pytest green (23), black + ruff clean, and a @e2b/python-sdk patch changeset is included.

handle_api_exception and envd get_message assumed a JSON error body is an
object: body["message"] raises TypeError when the body is null/a number, and
e.json().get(...) raises AttributeError when it is any non-dict (string,
array, scalar) -- neither caught by the surrounding JSONDecodeError guard.

A proxy or gateway in front of the control plane or envd can return an error
with Content-Type: application/json and a bare string or scalar body, which
then masks the real HTTP error with an opaque TypeError/AttributeError from
deep in the SDK. The JS SDK already tolerates these bodies (ApiError is typed
{ message?: string } | string). Guard for non-object bodies and fall back the
same way, so the HTTP error still maps to a SandboxException.
@cla-bot

cla-bot Bot commented Sep 17, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have @winklemad on file. You can sign our CLA at https://e2b.dev/docs/cla . Once you've signed, post a comment here that says '@cla-bot check'

@changeset-bot

changeset-bot Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c3e9527

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

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

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

TASTE.md review: checked T-1 (JS parity for non-object error bodies — matches res.error?.message ?? res.error / typeof res.error === 'string'), T-17 (widest input / isinstance grouping), T-57/T-60 (errors stay in the SandboxException hierarchy, mapping remains centralized in api/__init__.py and envd/api.py), T-62 (message preserves HTTP status and body text). 0 violations — the change complies.

Non-blocking observation: handle_api_exception treats a JSON "" body as no message (falls back to status: content), while envd.api.get_message returns the empty string for the same body. Not a TASTE rule, but aligning the two would keep the API/envd paths symmetric.

@winklemad

Copy link
Copy Markdown
Author

@cla-bot check

@cla-bot cla-bot Bot added the cla-signed label Sep 17, 2026
@cla-bot

cla-bot Bot commented Sep 17, 2026

Copy link
Copy Markdown

The cla-bot has been summoned, and re-checked this pull request!

Per review: an empty JSON string body now falls back to the raw text (as
handle_api_exception already does) instead of surfacing an empty message,
keeping the API and envd error paths symmetric.
@winklemad

Copy link
Copy Markdown
Author

Good catch on the asymmetry — aligned it in c3e9527: get_message now treats an empty JSON string as "no message" and falls back to the raw text, matching handle_api_exception. Added a test for it. Thanks!

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