From dlaw
This skill MUST be used before writing any implementation code — feature work, bug fixes, pipeline stages, data processing, API handlers, K8s manifests, or integration code. Enforces fail-loud patterns, input/output validation, connection verification, and pre-commit gates. Triggered automatically on any code writing task. Also use when user says "defensive", "fail-fast", "validate", "check failures", "harden".
How this skill is triggered — by the user, by Claude, or both
Slash command
/dlaw:defensive-codingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Stop writing only the happy path. Every piece of code must answer: **"What happens when this fails?"**
Stop writing only the happy path. Every piece of code must answer: "What happens when this fails?"
Before writing any code, complete the Defensive Checklist for that code. Do not skip items — each exists because a real production incident was caused by its absence.
Run through these checks BEFORE writing implementation code. For each check, either address it in the code or explicitly note why it doesn't apply.
Every function that receives external data must validate it before processing.
// BAD — trusts API response blindly
let price = response.price;
// GOOD — validates before use
let price = response.price;
if price <= 0.0 || price.is_nan() || price.is_infinite() {
return Err(anyhow!("invalid price {} for {}", price, ticker));
}
After producing output (writing files, publishing messages, inserting rows), verify the result.
// BAD — writes and moves on
await writeParquet(rows, path);
// GOOD — verifies what was written
await writeParquet(rows, path);
const stats = await readParquetMetadata(path);
if (stats.rowCount === 0) throw new Error(`Empty parquet at ${path}`);
if (stats.rowCount !== rows.length) throw new Error(`Row count mismatch: wrote ${rows.length}, file has ${stats.rowCount}`);
Never swallow errors. Never log-and-continue on critical paths. If something fails, make it visible.
try { } catch { log.warn(...) } on data paths — if data is missing, that's an error, not a warningunwrap_or_default() on critical data — a default value hides the bugif let Some(x) without handling None when None means broken state// BAD — silently returns empty on failure
let injuries = fetch_injuries().unwrap_or_default();
// GOOD — fails loudly
let injuries = fetch_injuries()
.map_err(|e| anyhow!("injury fetch failed: {e}"))?;
if injuries.is_empty() {
return Err(anyhow!("injury API returned empty response — expected data for today's games"));
}
On startup or first use, verify you're connected to the right thing.
// BAD — connects and hopes
let stream = nats.subscribe("prod.kalshi.*.json.ticker.>").await?;
// GOOD — verifies the subscription is on the right stream
let stream_info = js.stream_info("PROD_KALSHI_CRYPTO").await?;
info!("connected to stream {} with {} messages, subjects: {:?}",
stream_info.config.name, stream_info.state.messages, stream_info.config.subjects);
if !stream_info.config.subjects.iter().any(|s| s.contains("ticker")) {
return Err(anyhow!("stream {} has no ticker subjects — wrong stream?", stream_info.config.name));
}
Before committing code, verify it builds and passes tests locally.
make all (or the relevant build command) before committingcargo clippy, deno lint, etc.is_multiple_of — it's unstable in CI Docker images, use % N == 0.github/workflows/build-*.yaml trigger patterns# Before every commit
make all # or: cargo clippy && cargo test && deno lint && deno test
Document and verify assumptions at system boundaries.
| Writing... | Must Apply |
|---|---|
| Data processing / ETL | Input validation, Output assertions, Fail-loud |
| API handler | Input validation, Fail-loud, Boundary assumptions |
| Connector / subscriber | Connection verification, Fail-loud |
| Pipeline stage / CronJob | All six checks |
| K8s manifest / deployment | Connection verification, Pre-commit gates |
| Library / shared code | Input validation, Fail-loud, Boundary assumptions |
| Tests | Input validation (test data), Output assertions |
Reject these patterns in code review and never write them:
| Anti-Pattern | Why It's Dangerous | Write Instead |
|---|---|---|
catch (e) { log.warn(e) } | Hides failure, process continues with bad state | catch (e) { throw e } or crash |
.unwrap_or_default() on data | Produces empty/zero instead of surfacing the bug | .map_err(|e| ...)? with context |
if (data) { process(data) } (no else) | Silently skips when data is missing | Add else { throw } |
| Writing output without checking it | Corrupt/empty files go undetected | Read back and validate |
| Connecting without verifying target | Wrong stream/DB/endpoint for days | Health check + log target on startup |
| Committing without building | CI catches what you could have caught in 30 seconds | make all first |
For real production incidents that motivated each check:
references/failure-catalog.md — Catalog of real failures from this project, mapped to which checklist item would have caught themnpx claudepluginhub aaronwald/dlawskillz --plugin dlawApplies Code Complete defensive programming: barricade design, assertion vs error-handling, input validation policy, and correctness-vs-robustness strategy for the current code context.
Audits codebases for validation libraries, maps trust boundaries, detects unvalidated inputs, and verifies type-runtime alignment for data contracts.
Asserts internal assumptions (preconditions, postconditions, invariants) in functions, modules, or services to crash loudly at the violation site rather than propagating corrupt state downstream.