Skip to content
Arnel Robles edited this page Oct 20, 2026 · 3 revisions

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.

Hooks that block

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

What public-text.py counts

  • Attribution: a Co-Authored-By for 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 SLOP in lib/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, --input or -F body=@file.

What commit-author.py counts as local

A .local, .lan or .internal domain counts only when a remote is on github.com.

Hooks that note

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.

The lessons file

.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.

The same checks outside a session

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.

Clone this wiki locally