diagnostic errors
why
errors are the product's main channel for telling humans and machines an invariant failed. the invariant protected: the channel stays typed, stable, actionable, and safe. swallowing an error converts failure into a false business fact — "no results" instead of "the dependency is down" — misleading users, support, and product decisions at once, and hiding the failure from whoever could fix it. typed diagnostics at the narrowest proving boundary keep the repair loop closed while exposing nothing private.
rule
fail fast at the narrowest boundary that can prove an invariant false. errors crossing runtime or API boundaries are typed, stable, actionable, and safe for humans and machines. swallowed errors turn failures into false absence: a dependency times out, the code returns empty, the user sees "no results yet" and believes their data is missing — workarounds, support tickets, churn — while the team never learns the dependency was down. the false absence cascades into product decisions on incomplete information until a customer reports it as data loss.
required diagnostics
a diagnostic must carry:
- safe user message
- stable code
- operation or correlation ID
- structured context
- original cause in protected logs
- retryability
- correct remediation class
choose the failure class at the proving boundary
| observed failure | proving boundary | caller action |
|---|---|---|
| malformed request or impossible input | request/schema parser | correct the input; do not retry unchanged. |
| missing identity or permission | authorization boundary | authenticate, request access, or stop. |
| missing configuration or unsupported setup | integration/configuration boundary | correct a prerequisite. |
| timeout or dependency outage | dependency adapter | retry only when the operation is safe. |
| violated durable invariant | domain/persistence boundary | stop and investigate data or implementation. |
the same problem must not acquire a different public code merely because it crosses another layer. preserve the causal chain in protected logs; keep the public contract stable enough for the interface and repair loop to classify.
decision process
| shortcut | why it fails | selected direction |
|---|---|---|
| catch and return an empty value | turns a failure into a false business fact | fail at the narrowest proving boundary. |
| send raw provider text to the interface | exposes internals without a valid recovery path | map technical causes to a typed, safe diagnostic category. |
| use one generic error code | forces every caller to guess retry versus correction | encode stable remediation class and retryability. |
| log only a message string | loses the operation, resource, and causal chain needed for repair | retain structured context and the protected original cause. |
verification
force one representative failure per class. confirm the response gives a safe message, stable code, correlation identifier, and valid action; confirm protected logs connect that identifier to the original cause without exposing secrets to the caller.
do not
- swallow errors, return empty success, or hide a failure behind a fallback.
- expose secrets or unnecessary private data.
- replace a precise failure with "something went wrong."
good and bad
bad
try {return await readWorkflowRun(input);} catch {return null;}
an unavailable dependency becomes a false absence with no repair evidence: the dependency times out, the code returns null, the UI renders "no results yet." the user refreshes, checks whether their workflow ran, contacts support. support checks the database and finds the workflow did complete — the results-loading dependency was down for 3 hours. the user loses confidence (looks like data loss), tickets pile up, and the team never learns the dependency failed. the problem repeats tomorrow.
good
throw new DependencyUnavailableDiagnostic({operationId,retryable: true,cause,});
the boundary reports a stable, safe retry category; protected logs retain the original cause. the interface can honestly offer "retry" instead of a dead-end string, support classifies the ticket from the stable code and correlation id, on-call reaches the causal chain without the customer seeing internals. retry policies, alerting, and product decisions key off the typed class — the same outage stops being rediscovered.