Lesson¶
The skill that turns a mistake into a gate. Catches a signal that already flows past every day — the user correcting the agent — and converts it into the strongest artifact that will stop the mistake from recurring, in every repo where it applies.
Design intent: ride an existing signal (no new ritual), pay off immediately
(the artifact protects the next occurrence), stay narrow by default (project
before global), and let recurrence drive promotion up the enforceability
ladder. See ~/.claude/lessons/ledger.md for the running record.
When to use¶
- The user types
/lesson(optionally with a one-line description). - The user corrects the agent in a way that would generalize ("you didn't run the tests before saying done", "don't force-push without asking").
- The user says "remember not to…", "make this a rule", "so it never happens again", or "you keep doing X".
- At the end of a turn where the agent was visibly corrected, the agent should
offer
/lesson(soft nudge — the explicit command is the backbone).
When NOT to use¶
- One-off, context-specific corrections with no general form ("not that file, the other one") — nothing to enforce.
- World facts about people/projects/concepts → that's the agent-wiki / PKM, not a lesson.
- Workflow experiments where you change one variable to measure an effect — track those in your own experiment log, not here.
Procedure¶
Step 1 — Capture¶
Extract three things (ask only if not inferable from context):
- Trigger — what the agent actually did wrong, concretely.
- Lesson — the generalized rule, stated as an imperative.
- Root cause — why it happened (missing check, wrong default, no procedure).
Step 2 — Route: pick the FORM (try top-down, stop at first fit)¶
Bias up the ladder. The test at each rung is "does it fit?", not "is it convenient?".
- Hook — the lesson is a deterministic condition on a tool call, shell
command, or file path ("never/always X when Y"). Litmus test: could a shell
script read the tool input and decide pass/fail with no judgment? If yes →
hook. (e.g. "never
git push --force", "don't editgenerated/".) - Lint rule / test — a code-level invariant in a specific repo that a linter or test can assert.
- Skill — a repeatable multi-step procedure that keeps getting
reinvented in chat. Delegate authoring to the
write-a-skillskill. - Rule-line — judgment/taste that genuinely resists mechanization. The
explicit fallback, not the default. Lands in a
CLAUDE.md.
Step 3 — Route: pick the SCOPE (default project)¶
- Project (default) — anything stack- or repo-specific. Lands in the current repo.
- Global — only when the lesson is tool/stack-agnostic AND you have positive evidence it applies everywhere (e.g. git safety, secret handling), or it has already bitten in more than one repo. Globalizing is opt-in.
Cross-repo relevance is a trap: a lesson true in repo A is often wrong in repo B. When unsure, choose project. Promotion to global happens later, on evidence, via review mode.
Step 4 — Check the ledger before writing¶
Read ~/.claude/lessons/ledger.md. If a near-duplicate lesson exists:
- Increment its
hitscount instead of adding a duplicate row. - If
hitsreaches 3 and the form is stillrule-line, do not just bump — trigger promotion (see Review mode): propose the hook/lint/test that would make it impossible, and build it on approval.
Step 5 — Land the artifact¶
| Form + scope | Where it lands |
|---|---|
| hook · global | ~/.claude/settings.json (hooks) + guard script in ~/.claude/hooks/ |
| hook · project | <repo>/.claude/settings.json + <repo>/.claude/hooks/ |
| lint/test | the repo's lint config or test suite |
| skill · global | author via write-a-skill, install to ~/.claude/skills/<name>/ |
| skill · project | <repo>/.claude/skills/<name>/ |
| rule-line · global | ~/.claude/CLAUDE.md |
| rule-line · project | <repo>/CLAUDE.md (or <repo>/.claude/CLAUDE.md) |
Reuse before authoring: if an existing skill already encodes the lesson,
install/enable it rather than writing new (e.g. force-push → the installed
git-guardrails-claude-code skill).
Cursor mirror (secondary target): when the user works in Cursor too, mirror
the artifact — hook → .cursor/hooks.json; rule-line → a .cursor/rules/*.mdc;
skill → a .cursor/skills/<name> copy or symlink of the authored skill.
Step 6 — Append the ledger row¶
Always append (or update) one row in ~/.claude/lessons/ledger.md:
Step 7 — Report¶
Print the report template below.
Hook reference (Claude Code)¶
A PreToolUse hook that exits with code 2 blocks the tool call and feeds
its stderr back to the agent as the reason. This is the stable, judgment-free
gate.
~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "~/.claude/hooks/no-force-push.sh" }
]
}
]
}
}
~/.claude/hooks/no-force-push.sh (chmod +x):
#!/usr/bin/env bash
input=$(cat)
cmd=$(printf '%s' "$input" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))")
if printf '%s' "$cmd" | grep -qE 'git push .*--force'; then
echo "Blocked: force-push rewrites history. Confirm with the user first." >&2
exit 2
fi
exit 0
For file-path guards, match on Edit/Write and read
tool_input.file_path. Project-scoped hooks use the same shape under
<repo>/.claude/settings.json.
Review mode (/lesson review)¶
Run periodically once the ledger has rows. Two passes:
- Promote — any
rule-linewhosehits >= 3is escalated: propose the concrete hook/lint/test that makes the mistake impossible, build it on approval, and update the row'sform/artifact. A lesson that bites three times becomes a gate. - Prune — flag hooks that have never fired and rule-lines that never
recurred (
hits == 1and old) as deletion candidates, to keep the system from bloating into ignorable noise.
Report template¶
# Lesson captured
- **Trigger:** <what went wrong>
- **Lesson:** <the imperative>
- **Form:** <hook | lint/test | skill | rule-line> (chosen because …)
- **Scope:** <project:<repo> | global>
- **Artifact:** <path written / skill installed / hook registered>
- **Ledger:** row appended (hits=N)
- **Next time:** <what now prevents recurrence>
Anti-patterns¶
- Defaulting to a rule-line because it's easy — always test the higher rungs first.
- Globalizing by default — narrow scope is the default; global needs evidence.
- Capturing one-off corrections with no general form.
- Adding a duplicate ledger row instead of bumping
hits. - Writing a new skill/hook when an installed one already covers the lesson.
Compatibility¶
- Primary target: Claude Code (
~/.claude/). Secondary: Cursor (.cursor/). - Read/write inside the workspace, the current repo, and
~/.claude/. - No network egress. Destructive edits (settings.json, CLAUDE.md) are shown before writing.