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).
The code is for machines, and for search¶
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.