Skip to content

Commit 48687d8

Browse files
committed
docs(claude): tighten Error Messages; split examples into references doc
Replace the verbose Error Messages section with the junior-dev-tight version from socket-repo-template: same four ingredients, same audience-based length guidance, but rules are one-liners and only two short example pairs remain inline. Move worked examples and anti-patterns into `docs/references/error-messages.md` so CLAUDE.md stays under the 40 KB always-loaded ceiling.
1 parent f78d0af commit 48687d8

2 files changed

Lines changed: 112 additions & 22 deletions

File tree

CLAUDE.md

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -90,37 +90,35 @@ The umbrella rule: never run a git command that mutates state belonging to a pat
9090

9191
## ERROR MESSAGES
9292

93-
Errors are a UX surface. Every error message must let the reader fix the problem without reading the source. Four ingredients, in order:
93+
An error message is UI. The reader should be able to fix the problem from the message alone, without opening your source.
9494

95-
1. **What**: the rule that was violated (the contract, not the symptom)
96-
2. **Where**: the exact file, key, line, or record — never "somewhere in config"
97-
3. **Saw vs. wanted**: the offending value and the allowed shape/set
98-
4. **Fix**: one concrete action to resolve it
95+
Every message needs four ingredients, in order:
9996

100-
Find the balance between terse and meaningful — meaningful does not mean bloated:
97+
1. **What** — the rule that was broken (e.g. "must be lowercase"), not the fallout ("invalid").
98+
2. **Where** — the exact file, line, key, field, or CLI flag. Not "somewhere in config".
99+
3. **Saw vs. wanted** — the bad value and the allowed shape or set.
100+
4. **Fix** — one concrete action, in imperative voice (`rename the key to …`, not `the key was not renamed`).
101101

102-
- **Library-API errors** (thrown from the published package to its callers): terse. A caller catching and asserting on the message needs it short and stable. The four ingredients may collapse into one line; `name "__proto__" cannot start with an underscore` carries all four (what = start-rule, where = name component, saw = `__proto__`, fix implied by the rule).
103-
- **Validator / config errors** (build-time, developer-facing): verbose. The reader is staring at a file, needs to find the offending record, and won't re-run the tool to spot the next hit. Every ingredient gets its own words.
104-
- **Programmatic errors** (internal assertions, invariant checks): terse, rule-only. No caller will parse the message; terse keeps the check readable.
102+
Length depends on the audience:
105103

106-
Baseline rules that apply to all three:
104+
- **Library API errors** (thrown from a published package): terse. Callers may match on the message text, so every word counts. All four ingredients often fit in one sentence — e.g. `name "__proto__" cannot start with an underscore` covers rule, where (`name`), saw (`__proto__`), and implies the fix.
105+
- **Validator / config / build-tool errors** (developer reading a terminal): verbose. Give each ingredient its own words so the reader can find the bad record without re-running the tool.
106+
- **Programmatic errors** (internal assertions, invariant checks): terse, rule only. No end user will see it; short keeps the check readable.
107107

108-
- Write the fix step in the imperative (`add "filename" to part 3`), not passive narration (`"filename" was missing`).
109-
- Never say "invalid" without what made it invalid. `invalid filename 'My Part'` is a symptom; `filename 'My Part' must be [a-z]+ (lowercase, no spaces)` is a rule.
110-
- If two records collide, name both — not just the second one found.
111-
- Suggest, don't auto-correct. An error that silently repairs state hides the bug in the next run.
112-
- Bloat test: if removing a word loses information, keep it. If removing it loses only rhythm, drop it.
108+
Rules for every message:
113109

114-
Example — validator on `tour.json` (verbose form, one record per run):
110+
- Imperative voice for the fix — `add "filename" to part 3`, not `"filename" was missing`.
111+
- Never "invalid" on its own. `invalid filename 'My Part'` is fallout; `filename 'My Part' must be [a-z]+ (lowercase, no spaces)` is a rule.
112+
- On a collision, name **both** sides, not just the second one found.
113+
- Suggest, don't auto-correct. Silently fixing state hides the bug next time.
114+
- Bloat check: if removing a word keeps the information, drop it.
115115

116-
-`Error: invalid tour config`
117-
-`tour.json: part 3 ("Parsing & Normalization") is missing "filename". Add a single-word lowercase filename (e.g. "parsing") to this part — one per part is required to route /<slug>/part/3 at publish time.`
116+
Examples:
118117

119-
Example — library API error (terse form, caught in user code):
118+
-`Error: invalid config` → ✓ `config.json: part 3 is missing "filename". Add a lowercase filename (e.g. "parsing").`
119+
-`Error: invalid component` → ✓ `npm "name" component is required`
120120

121-
-`Error: invalid component` (misses what / saw)
122-
-`The "name" component of type "npm" failed validation because the provided value "" is empty, which is not allowed because names are required; please provide a non-empty name.` (bloated — restates the rule three times)
123-
-`npm "name" component is a required component` (rule + where + implicit saw=missing)
121+
See `docs/references/error-messages.md` for worked examples and anti-patterns.
124122

125123
## ABSOLUTE RULES
126124

docs/references/error-messages.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Error Messages — Worked Examples
2+
3+
Companion to the `## Error Messages` section of `CLAUDE.md`. That section
4+
holds the rules; this file holds longer examples and anti-patterns that
5+
would bloat CLAUDE.md if inlined.
6+
7+
## The four ingredients
8+
9+
Every message needs, in order:
10+
11+
1. **What** — the rule that was broken.
12+
2. **Where** — the exact file, line, key, field, or CLI flag.
13+
3. **Saw vs. wanted** — the bad value and the allowed shape or set.
14+
4. **Fix** — one concrete action, in imperative voice.
15+
16+
## Library API errors (terse)
17+
18+
Callers may match on the message text, so stability matters. Aim for one
19+
sentence.
20+
21+
| ✗ / ✓ | Message | Notes |
22+
| --- | --- | --- |
23+
|| `Error: invalid component` | No rule, no saw, no where. |
24+
|| `The "name" component of type "npm" failed validation because the provided value "" is empty, which is not allowed because names are required; please provide a non-empty name.` | Restates the rule three times. |
25+
|| `npm "name" component is required` | Rule + where + implied saw (missing). Six words. |
26+
|| `Error: bad name` | No rule. |
27+
|| `name "__proto__" cannot start with an underscore` | Rule, where (`name`), saw (`__proto__`), fix implied. |
28+
29+
## Validator / config / build-tool errors (verbose)
30+
31+
The reader is looking at a file and wants to fix the record without
32+
re-running the tool. Give each ingredient its own words.
33+
34+
`Error: invalid tour config`
35+
36+
`tour.json: part 3 ("Parsing & Normalization") is missing "filename". Add a single-word lowercase filename (e.g. "parsing") to this part — one per part is required to route /<slug>/part/3 at publish time.`
37+
38+
Breakdown:
39+
40+
- **What**: `is missing "filename"` — the rule is "each part has a filename".
41+
- **Where**: `tour.json: part 3 ("Parsing & Normalization")` — file + record + human label.
42+
- **Saw vs. wanted**: saw = missing; wanted = a single-word lowercase filename, with `"parsing"` as a concrete model.
43+
- **Fix**: `Add … to this part` — imperative, specific.
44+
45+
The trailing `to route /<slug>/part/3 at publish time` is optional. Include a *why* clause only when the rule is non-obvious; skip it for rules the reader already knows (e.g. "names can't start with an underscore").
46+
47+
## Programmatic errors (terse, rule only)
48+
49+
Internal assertions and invariant checks. No end user will read them;
50+
terse keeps the assertion readable when you skim the code.
51+
52+
-`assert(queue.length > 0)` with message `queue drained before worker exit`
53+
-`pool size must be positive`
54+
-`An unexpected error occurred while trying to acquire a connection from the pool because the pool size was not positive.` — nothing a maintainer can act on that the rule itself doesn't already say.
55+
56+
## Common anti-patterns
57+
58+
**"Invalid X" with no rule.**
59+
60+
-`Invalid filename 'My Part'`
61+
-`filename 'My Part' must be [a-z]+ (lowercase, no spaces)`
62+
63+
**Passive voice on the fix.**
64+
65+
-`"filename" was missing`
66+
-`add "filename" to part 3`
67+
68+
**Naming only one side of a collision.**
69+
70+
-`duplicate key "foo"` (which record won, which lost?)
71+
-`duplicate key "foo" in config.json (lines 12 and 47) — rename one`
72+
73+
**Silently auto-correcting.**
74+
75+
- ✗ Stripping a trailing slash from a URL and continuing. The next run will hit the same bug; nothing learned.
76+
-`url "https://api/" has a trailing slash — remove it`.
77+
78+
**Bloat that restates the rule.**
79+
80+
-`The value provided for "timeout" is invalid because timeouts must be positive numbers and the value you provided was not a positive number.`
81+
-`timeout must be a positive number (saw: -5)`
82+
83+
## Voice & tone
84+
85+
- Imperative for the fix: `rename`, `add`, `remove`, `set`.
86+
- Present tense for the rule: `must be`, `cannot`, `is required`.
87+
- No apology ("Sorry, …"), no blame ("You provided …"). State the rule and the fix.
88+
- Don't end with "please"; it doesn't add information and it makes the message feel longer than it is.
89+
90+
## Bloat check
91+
92+
Before shipping a message, cross out any word that, if removed, leaves the information intact. If only rhythm or politeness disappears, drop it.

0 commit comments

Comments
 (0)