Skip to content

Why errors have codes

sandboxio.errors.BackendNotInstalled: [SBX_E1002] The 'e2b' backend is not installed.
  Fix:  uv pip install "sandboxio[e2b]"
  Docs: https://<docs>/errors/SBX_E1002

Every exception this library raises carries three things beyond its message: a stable code, a hint that is the exact fix, and a URL. Here is why each exists (spec/04, ADR-0010).

Two audiences read errors. Humans want the fix. Coding assistants — increasingly the ones writing sandboxio calls — want a token that means the same thing this year and next. Messages get reworded; codes do not. SBX_E1302 is ExecutionTimeout today and will be until v1 ends, which means a search, a runbook entry or an except clause written against the code keeps working. Renaming or repurposing a code is a breaking change under semver, like removing a method.

The hint is the fix, not the problem restated

A hint that says "the backend is not installed" adds nothing. A hint that says uv pip install "sandboxio[e2b]" ends the incident. So errors are constructed with context rather than raised bare: AuthError names the exact environment variable, BackendNotFound lists what is installed, CapabilityNotSupported names the backends that do support the call. The error catalog is generated from the source so the pages and the hints cannot drift.

No inheritance from builtins, on purpose

except TimeoutError does not catch a sandbox timeout. On Python 3.11+ the builtin TimeoutError is what asyncio.timeout() raises, so with an outer deadline around a sandbox call the two mean different things: the caller's deadline fired, or the sandbox's own limit fired. Inheriting would erase that distinction. Instead SandboxTimeout is a codeless base that catches every sandbox deadline, with CreateTimeout and ExecutionTimeout beneath it (ADR-0017). And SandboxGone is deliberately not a timeout: the sandbox's lifetime ended while you were using it, which is a different fix.

Messages never carry secrets

Redaction runs before anything is rendered: every value passed as secrets= and every credential-shaped environment value is replaced wherever it appears — in messages, notes, reprs, audit events, spans and CLI output. An exception you log is safe to log.

The catalog

docs/errors/ has one page per code and is regenerated by scripts/gen_error_catalog.py; a CI gate fails when the pages and the source disagree.