defensive-patterns
Defensive Patterns
A catalogue of bug-class rules distilled from defects that actually shipped. Each pattern is one class of failure, stated as the rule that prevents its recurrence. Read this before writing lifecycle, concurrency, subprocess, teardown, or error-reporting code — these are the places where "it usually works" hides a reproducible race.
The defining constraint: these rules are about settlement — the honest, complete reporting of an outcome — and ownership — knowing who owns a resource until it is fully gone. Most concurrency bugs are a settlement or ownership violation in disguise.
This is guidance, not a script. Each pattern names a failure mode and the positive rule that avoids it; apply the ones your code touches. Patterns cluster into two groups — reporting and ownership — with one untrusted-IO trio.
Reporting — tell the whole truth about an outcome
Report orthogonal outcomes independently
A result can be several things at once. A process can time out and exit 0 because it trapped the signal; a call can fail and leave valid partial state. Surface each independent fact (timedOut, signal, exitCode) on its own field or branch — never nest one flag's report inside another's branch, or a caller reads a cut-short run as a clean success. When two facts can both be true, the code that reports only one is lying.
Honor public contracts on BOTH sides
When an implementation receives several representations of one outcome, normalize them before returning through the public API. If a provider may signal failure by throwing or by emitting an error token, the public runtime exposes model-request failures as exactly one form; middleware and caller defects remain thrown. Consumers must never guess whether a caught exception came from the provider, a wrapper, or their own assembly. Document the normalized contract where the type is defined, and exercise every source form through the real consumer.