From backend-engineering
Playbook for modeling and propagating errors in backend services — typed result/error envelopes, failure classification, HTTP status mapping, error translation at layer boundaries, and client-safe vs internal error separation. Prevents exception-driven spaghetti and accidental information leakage.
How this skill is triggered — by the user, by Claude, or both
Slash command
/backend-engineering:error-handling-and-result-modelingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Every failure belongs to exactly one category. The category drives the handling strategy.
Every failure belongs to exactly one category. The category drives the handling strategy.
| Category | Meaning | Retry? | Expose to client? |
|---|---|---|---|
| Validation | Caller sent bad input | No | Yes — full detail |
| Not found | Requested resource absent | No | Yes — safe message |
| Conflict | State mismatch (optimistic lock, duplicate) | No | Yes — safe message |
| Unauthorized / Forbidden | Auth failure | No | Minimal detail |
| Downstream | Dependency failed or timed out | Yes (idempotent) | Opaque service_unavailable |
| Bug / unexpected | Unhandled code path | No | Opaque internal_error |
Avoid throwing exceptions across use-case/service boundaries. Return a typed result:
// TypeScript example — adapt to your language
type Ok<T> = { ok: true; value: T };
type Err<E> = { ok: false; error: E };
type Result<T, E> = Ok<T> | Err<E>;
# Python example using dataclasses
@dataclass
class Ok(Generic[T]):
value: T
@dataclass
class Err(Generic[E]):
error: E
Result = Ok[T] | Err[E]
Rules:
Result<DomainValue, DomainError> — never throw for expected failures.HTTP handler
└── Use-case / service (returns Result<T, DomainError>)
└── Repository / client (returns Result<T, InfraError>)
At each boundary, translate inward-facing errors to the layer's vocabulary:
InfraError.DbConnectionFailed → DomainError.ServiceUnavailable
InfraError.UniqueConstraintViolated → DomainError.Conflict(entity, key)
The HTTP handler is the only layer that maps DomainError → HTTP status + JSON body.
| DomainError category | HTTP status | When |
|---|---|---|
| Validation | 400 | Bad request body/params |
| Not found | 404 | Resource absent |
| Conflict | 409 | Duplicate / stale update |
| Unauthorized | 401 | Missing / invalid credential |
| Forbidden | 403 | Authenticated but not allowed |
| Downstream | 503 | Dependency unreachable |
| Bug / unexpected | 500 | Anything else |
{
"error": {
"code": "PAYMENT_METHOD_EXPIRED",
"message": "The payment method has expired.",
"details": [{ "field": "card.expiry", "issue": "past_expiry" }]
}
}
Rules:
code is a stable machine-readable slug — don't change it after release.message is English prose safe to display (no stack traces, no internal state).details is optional; present only for validation errors with field-level specifics.| Error category | Log level | What to include |
|---|---|---|
| Validation / not found | DEBUG or omit | High-volume, expected — log at trace if needed |
| Downstream (retried, eventually ok) | WARN | Attempt count, dependency name, duration |
| Downstream (exhausted) | ERROR | Full context, trace ID, retries attempted |
| Bug / unexpected | ERROR + alert | Stack trace, request ID, all context |
{ error: null, data: null } — ambiguous; force the caller to pick a branch.psycopg2.OperationalError: ... or ECONNREFUSED in the response body — exposes topology.npx claudepluginhub mcorbett51090/ravenclaude --plugin backend-engineeringGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.