Error-Handling Style

Expected failures are values; bugs are exceptions. Validate at the edges, never swallow an error silently, and never paper over one with a fallback that hides it.

This is the cross-cutting policy that the language-specific guides (Typescript) and the safety concepts (No-Fallbacks Policy, Dual-Audience Errors) point at. Source: compiled from TypeScript Style, 2026-05-31

Expected failures return, bugs throw

Model expected, recoverable outcomes (record not found, validation failed, payment declined) as a Result<T, E> and force the caller to handle both arms. Reserve throw for genuinely exceptional, unrecoverable states (invariant violated, programmer error). Do not use exceptions for ordinary control flow, and do not return null to mean five different failures. Source: TypeScript Style, 2026-05-31

Validate external input at the boundary

Every value crossing a trust boundary, request body, query params, env vars, third-party responses, is parsed with a Zod schema before use (see Zod Validation Style). Inside the trust boundary, data is already typed; do not re-validate trusted internal calls. Source: TypeScript Style, 2026-05-31

Never swallow, never blanket-fallback

  • No empty catch {}. If you catch, you handle, rethrow, or log with context.
  • catch (error: unknown), then narrow with error instanceof Error. Do not assume the caught type.
  • No silent fallback that masks a real failure. A try/catch that returns a default on any error turns an outage into a wrong answer. Fail closed and surface the error instead (see No-Fallbacks Policy).

Source: No-Fallbacks Policy, 2026-05-31

Errors speak to two audiences

An error object carries a clean user-facing message and structured developer detail (cause, origin, machine-readable code). Map internal errors to safe external messages at the system edge, never leak stack traces or internals to the client (see Dual-Audience Errors). Label console.warn/console.error with origin, [FileName::functionName::L42], for traceability. Source: Dual-Audience Errors; TypeScript Style, 2026-05-31

Shape and flow

  • Prefer early returns over deeply nested try/catch; handle the error case first and keep the happy path unindented.
  • In React, use Error Boundaries for render-time failures; do not try to catch what a boundary should own.
  • Let a Result propagate up to the edge (an API route or Server Function), and map it to an HTTP status or UI state there, not in the middle of business logic.

Error Contract

Failure kind Default shape Edge mapping
Validation failure Typed parse error with field/path detail 400 or form field state
Missing resource Domain NotFound result 404 or empty-state branch
Conflict Domain Conflict result with safe reason 409
Permission failure Auth/authz result 401 or 403
Provider failure Internal error with provider context Safe external message plus logged cause
Invariant violation Exception or panic with origin Error boundary, 500, alert, or failed job

Proof

Tests should assert both arms of expected failures. UI proof should show the user-facing state. Logs or traces should preserve the developer context. A failure path that cannot be tested or observed is not a style exception; it is an unfinished interface.

Style Rule Contract

This page follows Style Rule Contract: name the default pattern, banned pattern, reason, exception, proof, and codification path clearly enough that the next agent can apply the rule without asking Kevin again. If a local repo has a stricter rule, follow the local rule and update the wiki when the decision becomes durable.


Timeline