Repository navigation
Hooks
Three hooks block. The rest add a note for the agent and never stop the call. A hook's own bug never breaks the tool call it watches: any internal error exits 0.
Each has an off switch. Set it in the env block of your settings, or inline in front of one Bash command.
| Hook | Event | Blocks | Off switch |
|---|---|---|---|
public-text.py |
Before Bash | A commit, tag, pull request, issue, release or gh api write whose text carries agent attribution or slop |
LEAN_ALLOW_ATTRIBUTION=1, LEAN_ALLOW_SLOP=1
|
commit-author.py |
Before git
|
git commit when the author email is unset, ends in a local host name, or is a users.noreply.github.com address without the numeric id |
LEAN_ALLOW_COMMIT_AUTHOR=1 |
scratchpad-root.py |
Before Write | Writing pr-body.md, body*.md, notes.md or plan.md straight into a directory named scratchpad, or the directory in CLAUDE_SCRATCHPAD
|
LEAN_ALLOW_SCRATCHPAD_ROOT=1 |
-
Attribution: a
Co-Authored-Byfor an AI tool, a session link, a "Generated with" line. Checked in the whole command. -
Slop: em and en dashes, arrow glyphs, and the filler words in
SLOPinlib/house_style.py. Checked only in message values (-m,--title,--body,--notes, field values, message files, a heredoc fed to-F -), with code spans removed. - It reads message files passed with
-F,--body-file,--notes-file,--inputor-F body=@file.
A .local, .lan or .internal domain counts only when a remote is on github.com.
| Hook | Event | Notes | Off switch |
|---|---|---|---|
brain-resume.py |
Session start (startup and after a compaction) | The open Brain items of the project, as a short digest: blockers, running agents, queued jobs, pending steps, questions. Silent when there is no database or nothing is open. It tells the agent not to work around a blocker. | LEAN_BRAIN_RESUME=off |
session-env.py |
Session start, session end, each prompt | At start it writes LEAN_SESSION_ID and LEAN_SESSION_CWD into the session environment and records the session as open in brain. At end it records it as ended. On each prompt it hands the session any answers to its questions that it has not had yet. See Lean Brain Extension. |
LEAN_SESSION_ENV=off |
mode.py |
Session start | The working mode, when a setting is not the default. See Settings. | Leave the settings at their defaults |
style-note.py |
After Edit or Write | Slop and attribution in text just written to a .md, .txt or message-like file |
LEAN_SKIP_STYLE_NOTE=1 |
record-test-run.py |
After Bash | Nothing. It records each passing test run (dotnet test, npm test, go test, pytest, cargo test) for the next hook. dotnet test --no-build, cargo test --no-run and a run piped into tail are not recorded. |
LEAN_SKIP_TEST_BEFORE_PUSH=1 |
test-before-push.py |
Before git push, gh pr create, gh pr merge
|
Changed source files edited since the last recorded passing run, or that no run is recorded | LEAN_SKIP_TEST_BEFORE_PUSH=1 |
verify-claim.py |
Before gh issue comment, gh pr comment, gh pr review
|
Asks whether each claim was checked. Quiet for a body under 30 words. | LEAN_SKIP_VERIFY_CLAIM=1 |
docs-containers.py |
Before docker compose up/run, docker run
|
That every changed file is documentation, so containers are probably not needed | LEAN_SKIP_DOCS_CONTAINERS=1 |
lesson-log.py |
After git commit
|
Appends the commit to .lean/lessons.tsv when its subject says fix, revert, flaky, silent, gate or regress |
LEAN_SKIP_LESSON_LOG=1 |
pr-merge-blocked.py |
After a failed or refused gh pr merge (also when a pipe or an or true hid the exit code) |
Points the agent at pr-blockers.sh
|
LEAN_SKIP_PR_MERGE_BLOCKED=1 |
Every hook records each block or note it gives in the lean-stats database: the hook, the outcome, the session id and the working directory, never the command. A failed write is dropped and never touches the tool call. LEAN_STATS=off stops it. See Measuring a Batch.
.lean/lessons.tsv is UTF-8, one commit per line, no header, three tab-separated fields:
| Field | Content |
|---|---|
| date |
YYYY-MM-DD, the day the commit was logged, local time |
| sha | The full commit hash. A hash already in the file is not added again. |
| subject | The commit subject, with tabs and newlines turned into spaces |
The first write adds /.lean/ to info/exclude in the git directory, so the log stays out of git add -A without touching .gitignore. Only a commit made in the last five minutes is logged. "fixture" and "gateway" do not count as matches.
The Retro reads it.
style-scan runs the slop, attribution and U+2000 checks over the lines a branch adds, untracked files included, for a preflight or CI step. See Scripts.
Start
The method
- 4-4a. Scripts, not instructions
- 5-6. Rework and concurrency
- 14a-14h. Field notes from parallel agents
Brain and the test queue
How the plugin runs it
- Shaping a Ticket
- Models and the Cascade
- Adversarial Review
- Review Rules
- Holdout Check
- Measuring a Batch
- Retro
Reference