Standard Error Envelope
so1 beginner 4 min read
ELI5
Every error ships in the same little box: a tracking number on the outside (requestId), a colour-coded label inside (code), a polite note for the customer (message), and a sealed envelope for engineers (details).
Technical Deep Dive
Shape
classDiagram class ErrorEnvelope { +requestId : string (uuid) +error : ErrorBody } class ErrorBody { +code : string +message : string +details? : Record~string,unknown~ } ErrorEnvelope --> ErrorBodyPer ADR-004:
{ "requestId": "550e8400-e29b-41d4-a716-446655440000", "error": { "code": "FORBIDDEN", "message": "You do not have permission to view this repository.", "details": { "upstream": { "statusCode": 403, "error": "Resource not accessible by integration", "url": "https://api.github.com/repos/org/private-repo" } } }}Envelope vs Raw Body
- Errors are always wrapped in the envelope (parsed when
!response.ok). - Success responses are raw payloads (e.g.,
{ repos: [...] }) plusX-Request-Idheader.
The client distinguishes by HTTP status, not by inspecting the body.
Field Rules
| Field | Audience | Notes |
|---|---|---|
requestId | support / engineers | UUID v4, also in X-Request-Id and logs (so1-012) |
error.code | client logic | machine-readable, stable enum (so1-011) |
error.message | end user | non-technical; bugs hidden in details |
error.details | engineers | optional; may contain redacted upstream payloads |
Redaction (ADR-004)
error.message and error.details MUST never contain: API keys, tokens, secrets, raw stack traces, file paths, or DB query errors. The reference redactError strips Bearer …, sk-…, and /home/…/ patterns.
Key Terms
- Envelope → wrapping object that adds metadata around a payload.
- requestId → UUID tying envelope to logs and upstream traces.
details→ free-form bag for engineer-only context.
Q&A
Q: Why are success responses not wrapped?
A: ADR-004 explicitly trades wrapping consistency for smaller success payloads; clients still get tracing via the X-Request-Id response header.
Q: Is details required?
A: No — it is optional. Auth errors typically omit it; upstream-mapped errors fill it.
Q: How is the envelope produced?
A: ADR-004 names a buildErrorEnvelope() helper in @so1/shared/src/errors.ts invoked by the error handler middleware (so1-004).
Examples
A failing call from so1-rover/src/lib/api-client.ts parses { requestId, error }, throws ApiError(error.message, { code: error.code, requestId, details }). The error boundary renders the message plus the requestId so a user pasting it into Slack gives engineers a one-step path to the log line.
neighbors on the map
- ProtocolMessage Envelope adding a new wire message type
- EventEnvelope Wire Wrapper publishing a new domain event proto
- purr-api Layered Architecture adding a new feature to purr-api