Distilled from 249 principles in learning.db — the ones that earn their keep
You've accumulated 249 principles from hundreds of coding sessions. Each one was hard-earned — a bug that burned you, a design that crumbled, a pattern that saved hours. They live in learning.db, materialized to ~/.claude/principles/, and are automatically retrieved when they seem relevant.
But 249 is too many to hold in your head. The truly transformative ones — the seven that should run in your background process on every task — get buried alongside niche observations about SQLite triggers and Gradio CSS.
This presentation ranks the top 30 by a single criterion: how much damage does violating this principle cause, and how broadly does it apply?
Selection criteria, in order of weight:
| Criterion | What it measures |
|---|---|
| Blast radius of violation | How much damage when you ignore it? Silent failures (Tier 1) cause more damage than poor naming (Tier 4) |
| Breadth of application | Every task? Most tasks? Certain domains only? Tier 1 applies to everything |
| Non-obviousness | "Test your code" is true but obvious. "Workarounds piling up = wrong abstraction" is non-obvious and actionable |
| Evidence depth | Number of linked instances in learning.db — principles with more real examples rank higher |
Here's how the data backs these rankings. The top 30 were selected from 249 active principles in learning.db, supported by 6,130 instances and 3,644 tracked applications. The evidence is not uniform — some principles (#15 Verify Action: 353 applications) are battle-tested across dozens of sessions, while others (#21 Map Boundaries: 0 applications) are conceptual frameworks that haven't yet been tracked in production. Both are valuable, but the ranking weights empirical evidence.
A swallowed exception is a lie to your future self. The cost of a visible error is one interrupted task. The cost of a hidden error is hours of debugging the wrong thing, or worse — shipping broken output that looks correct.
Negative test: If removing this principle would make your codebase quieter but not more correct — that's the point.
Not just "don't add what you don't need" (YAGNI) but also audit what's already there. A reload flag that toggles between hot-reload and no-reload? Nobody wants the no-reload path. Delete it. Three similar lines of code is better than a premature abstraction.
If a library manages state, don't also manage it. If a framework owns the event loop, don't fight it with threading. Every boundary violation creates a maintenance coupling that compounds.
CSS hacks, MutationObservers, custom JS to fix layout — these are symptoms. The disease is wrong abstraction. When workarounds pile up, the cost of continuing to patch exceeds the cost of a rewrite. Recognize the signal.
Replacing a 200-line file when you needed to change 3 lines discards context, comments, careful formatting, and potentially introduces regressions. Surgical edits are reviewable; replacements are not.
Before every action, ask: "If this goes wrong, what breaks?" A function change breaks one caller. A module rename breaks many. A force-push breaks everyone. Scale your caution to the blast radius.
Logging that nobody reads, metrics that nobody alerts on, props that no op reads — all dead weight. Follow each data element to its consumer. No consumer? Delete the producer.
The first version of your custom tool is faster to write. The 10th bug fix won't be. Check: stars + recency, scope match (80% is good enough), integration cost (5-line import beats a fork).
Design expands to cover cases. Simplification contracts to the essential. Both passes are required. The simplification pass is where "6 parameters" becomes "2 parameters and a sensible default."
Read the docs, search the codebase, check if someone solved this. The urge to start coding immediately is strong and usually wrong. The codebase already has a pattern for this — find it first.
If "model selection" and "output format" vary independently, they should be separate parameters — not bundled into recipe variants like ask_json and ask_text. Orthogonal axes compose; coupled axes explode combinatorially.
An empty list returned instead of raising is a silent failure. A try/except: pass is a silent failure. A missing else clause on a match statement is a silent failure. Each one is a time bomb.
Every "we'll migrate later" becomes permanent dual maintenance. The old system never dies — it just gets increasingly neglected while consuming support effort. Migrate fully or don't start.
Auth logic and rendering logic in the same function means changing auth requires understanding rendering. Separate the dimensions; let each evolve independently.
Wrote a file? Read it back. Started a server? Hit the health endpoint. Ran a migration? Check the schema. The gap between "I called the function" and "the effect happened" is where bugs hide.
The extreme proposal isn't a mistake — it's a diagnostic. "Flatten everything to dicts" maps exactly where genericity helps and where it breaks. Neither the conservative status quo nor the maximalist proposal reaches the synthesis alone.
Replacing exhausted() (boolean) with frac() (0.0–1.0) enables throttling before the wall, proportional allocation, and gradual effort adjustment. Every binary check is a continuous signal with information discarded.
Item.props and Context.budget both "hold metadata in a dict." But one is an extension boundary (generic, flexible) and the other is an engine boundary (typed, validated). Unifying them on the metaphor destroys the mechanics each needs.
Vario's Budget had a binary exhausted() method. Eight callsites checked it. The problem: binary signals can't enable proportional decisions.
if ctx.budget.exhausted():
# stop everything
return
Binary. Either working or dead. No gradual response.
if ctx.budget.frac() >= 1.0:
return # same behavior
# BUT now you can also:
if ctx.budget.frac() > 0.8:
# throttle, reduce effort
n = max(1, round(5 * remaining))
Continuous. Same info at the boundary, richer everywhere else.
Principles applied: #27 (continuous > discrete), #9 (simplify — exhausted() was eliminated, not kept alongside), #7 (trace to action — frac() feeds actual allocation decisions).
Vario's Item uses a generic props dict. Context uses named fields (budget, hooks). The inconsistency seemed like a design flaw.
Extreme probe (#21): "Make Context generic like Item — everything is dict[str, Any]." Ran it through 3-model adversarial review.
What broke: Engine boundaries need validation. limit.usd as a string key means a typo silently removes your budget cap. Concurrency safety requires encapsulation, not raw dicts.
Synthesis (#28): The metaphor (both hold metadata) was shared, but the mechanics (extension vs engine control) were different. Result: "unopinionated at extension boundaries, opinionated at engine boundaries."
A YouTube channel download pipeline processed 2,000 videos. Two hours in, it silently failed at item 847 — but reported overall "success" because the orchestrator only checked the final HTTP status, not per-item outcomes.
result = batch.run(urls)
# returns 200 OK even when
# 40% of items failed silently
if result.status == 200:
log("batch complete")
Aggregate success masks per-item failures. 847/2000 items lost.
for url in urls:
result = process(url)
report.record(result)
# End-of-batch reconciliation
assert report.succeeded >= report.expected * 0.95
Each item tracked. Batch continues on failure. Summary reveals the gap.
Principles applied: #12 No Silent Failures (256 applications — the most-evidenced principle in the system) → per-element error tracking. #15 Verify Action (353 applications) → end-of-batch reconciliation. #24 Fail Per-Element → individual failures don't kill the batch.
A Gradio monitoring dashboard displayed 50+ metrics in a data table. Users couldn't find what mattered — every metric competed equally for attention. Worse: Unicode zero-width spaces used for column alignment caused silent query failures when the data was parsed back from the UI.
Less Is More (#18): Reduced visible metrics from 50 to 5 — the ones that drive actual decisions. Trace the Chain (#7): For each remaining metric, identified the specific action it informs. Metrics with no consumer were hidden. Minimize Blast Radius (#6): Hidden metrics moved to an expandable section, not deleted. Previous users can still find them.
Result: dashboard load time dropped 4x, and the Unicode bug surfaced immediately because the parsing layer now handled fewer, well-defined fields.
An integration test failed intermittently — sometimes on CI, never locally. The instinct was to add retries and increase timeouts.
# Reaching behind the API to check DB directly
items = db.query("SELECT * FROM items")
assert len(items) == 3 # race with async write
Bypasses the abstraction boundary. Race condition with async commit.
# Verify through the same API the user sees
resp = client.get("/api/items")
assert len(resp.json()) == 3 # waits for commit
Tests what the user sees. No race — the API waits for consistency.
Principles applied: #17 Treat Symptoms as Signals — the flakiness was the bug signal, not the specific assertion. #3 Respect Abstraction Boundaries — the test reached behind the HTTP layer to query the database directly, creating a race condition. #15 Verify Action — replaced with end-to-end verification through the API the user actually calls.
Internalize Tier 1. These 7 should be reflexive — you shouldn't have to think about them. Print them on a card. If you can only remember one: Fail Loud, Never Fake.
Consult Tier 2–3 when designing. Before committing to an architecture, check: Am I decomposing orthogonally? Did I simplify after designing? Am I building when I should be adopting?
Tier 4 activates contextually. Starting a batch pipeline? Scan #24–26. Designing a data structure? Check #27–28. The /recall command does this automatically.
They live in learning.db, materialized to ~/.claude/principles/. The /recall skill searches them semantically before any design decision. You don't need to memorize them — the system surfaces them when relevant.
All 30 principles on one sheet. Print it. Pin it.
| # | Principle | One-liner | Tier | Evidence | Applied |
|---|---|---|---|---|---|
| 1 | Fail Loud, Never Fake | Never swallow errors or return fake success | T1 | 10 | 55 |
| 2 | Complexity Must Be Earned | Every abstraction needs a concrete user | T1 | 5 | 13 |
| 3 | Respect Abstraction Boundaries | Never reach behind what a library manages | T1 | 15 | 31 |
| 4 | Workarounds = Wrong Abstraction | Hacks piling up? Redesign | T1 | 10 | 22 |
| 5 | Edit, Don't Replace | Surgical edits, not wholesale rewrites | T1 | 1 | 38 |
| 6 | Minimize Blast Radius | Scale caution to scope of impact | T1 | 10 | 40 |
| 7 | Trace the Chain to an Action | Data without a consumer is noise | T1 | 2 | 4 |
| 8 | Adopt Before You Build | Search before writing custom code | T2 | 7 | 147 |
| 9 | Simplify After Designing | Deliberate simplification pass on every API | T2 | 13 | 0 |
| 10 | Research Before Building | 30 min research saves hours of rework | T2 | 7 | 52 |
| 11 | Orthogonal Axes | Independent concerns = independent config | T2 | 23 | 21 |
| 12 | No Silent Failures | If something breaks, it must be visible | T2 | 31 | 256 |
| 13 | Migrate, Don't Straddle | Complete the migration or don't start | T2 | 4 | 4 |
| 14 | Independent Dimensions | Things that vary independently live separately | T2 | 5 | 26 |
| 15 | Verify Action | After doing, verify it worked | T2 | 12 | 353 |
| 16 | Draft Shape First | Interfaces before implementation | T3 | 0 | 7 |
| 17 | Symptoms as Signals | Fix the cause, not the symptom | T3 | 18 | 9 |
| 18 | Less Is More | Best code is code you don't write | T3 | 5 | 15 |
| 19 | Extend, Don't Invent | Build on existing patterns | T3 | 4 | 115 |
| 20 | Minimum Necessary Weight | Lightest mechanism that solves it | T3 | 8 | 27 |
| 21 | Extreme Probes | Maximalist proposal maps where it breaks | T3 | 0 | 0 |
| 22 | Recognize Poor Fit | When a tool fights you, switch tools | T3 | 10 | 69 |
| 23 | Principles Persist | Invest in the why, not the what | T3 | 7 | 27 |
| 24 | Fail Per-Element | One bad item shouldn't kill a batch | T4 | 6 | 29 |
| 25 | Idempotency | Safe to retry, resume on crash | T4 | 4 | 39 |
| 26 | Progressive Trust Radius | Start small, verify, expand | T4 | 3 | 1 |
| 27 | Discrete vs Continuous | Continuous signals enable proportional decisions | T4 | 1 | 0 |
| 28 | Metaphor vs Mechanics | Similar-looking things may need different treatment | T4 | 9 | 0 |
| 29 | Generalize From Failures | One fix should prevent the whole class | T4 | 7 | 2 |
| 30 | Classify Before Processing | Triage first, then route to handlers | T4 | 0 | 0 |
Generated 2026-03-31. Source: learning.db (249 active principles, 6,130 instances, 3,644 tracked applications). Ranked by blast-radius × breadth × non-obviousness × evidence-depth. Evidence badges show per-principle stats from production usage.
View at: static.localhost/present/gallery/coding_principles/