Claude Learner
Claude Learner pairs Claude Code hooks with a skill to quiz you on the
code Claude just wrote with you, at the level you set, and keep a record of what you
should work on (read it back any time with
learner status). It installs once at Claude Code user level and is then
active in every git repository you open, with no per-repo setup.
One skill, six POSIX sh hooks, no build step, and no
dependency beyond jq — except a once-a-day, best-effort version check
that needs curl and reaches out to GitHub. The source is on
GitHub.
The problem
Code now arrives faster than understanding does. Reviewing what an agent wrote is easy to skip and hard to do well — the diff is plausible, the tests are green, and the next task is already queued — so the honest answer to “do you know how this works?” drifts quietly from yes to nearly. A question you have to answer before the turn ends is not skippable in the same way. Learner spends one question of your attention at the end of a turn, while the code is still fresh, and writes down whatever you did not know so it can come back to it later.
What it actually looks like
Nothing below is a mock-up. The trigger lines are the text
hooks/learner-quiz.sh emits, and the fill exercise cuts up a
function from this project's own source.
A granular question
When a turn ends and the session has edited at least one file inside the repository,
the Stop hook blocks once. What lands in the console is the hook's own
reason:
Stop hook's block reason🎓 Learner (level: C, mode: granular, styles: auto, blanks: 2) — files: /Users/you/code/api/src/auth/session.ts
Invoke the `learner` skill, follow references/hook-quiz.md for this mode. Ask ONE question, then wait for the dev's answer.
That line is parameters, not the question — the protocol lives in the skill, so the hook never has an opinion about what a good question is. Claude reads the files, reads your open weak spots, and asks one question pitched at the level named:
🎓 Learner — src/auth/session.ts
You changed refreshSession() to delete the old session row before inserting
the replacement. Why that order rather than insert-then-delete, and what
does a caller see if the insert fails after the delete has committed?
Answer it and the turn continues. Say skip and it moves on without
insisting. Either way what you did not know is recorded, and the next question prefers
a weak spot that is still open — spaced repetition over the code you actually
write.
A synthesis question
Every fourth question by default, one question is about the shape of the work rather than a detail. The hook switches mode and widens the file list: a granular question sees the edits since the last question, a synthesis question sees every file the session has touched — each list capped, at 20 files and 40 files respectively, so the trigger line itself never grows unbounded on a very large session.
🎓 Learner (level: C, mode: synthesis, styles: auto, blanks: 2) — files: /Users/you/code/api/src/auth/session.ts /Users/you/code/api/src/auth/tokens.ts /Users/you/code/api/src/db/queries.ts
Invoke the `learner` skill, follow references/hook-quiz.md for this mode. Ask ONE question, then wait for the dev's answer.
🎓 Learner — this session
Trace what happens between a request arriving with an expired access token
and its caller getting a fresh one. Which of the three files you touched
owns each step, and why does the rotation live in tokens.ts rather than in
session.ts?
A fill exercise
This is the style that is hardest to picture from a description and the one that most reliably tells you whether you understood the code. Claude picks one short function among the files you just edited, memorises the correct version from git, then cuts holes in the real file on disk and asks you to fill them in.
Here is learner_synthesis_n from this project's own
hooks/learner-config.sh, whole — it turns the
synthesisFrequency word into the number of questions between synthesis
questions:
hooks/learner-config.shlearner_synthesis_n() {
case "${1:-}" in
off) printf '0' ;;
rare) printf '8' ;;
often) printf '2' ;;
*) printf '4' ;;
esac
}
And the same function after a fill exercise with
blanksPerExercise at its default of 2. This is not a preview pane: it is
your file, saved to disk, and your editor will show it changed.
learner_synthesis_n() {
case "${1:-}" in
off) printf '0' ;;
// LEARNER-TODO: the two named cadences, and which one is rarer
// LEARNER-TODO: the fallback arm — what an unrecognised word gives
esac
}
Claude names the file and the function, asks you to write the missing code
in the file, and then waits. It will not fill them in for you. When
you are done — or you say skip — it compares what you wrote against the
version it memorised, gives you a couple of lines of feedback, restores a correct
implementation, and runs a focused check that the file is valid again.
Two things worth knowing before you let it do that. The marker is the same
literal string in every language, // LEARNER-TODO, so in a language
where // does not start a comment — shell, as above — the file does not
merely have a gap in it: it does not run until the exercise is finished. And the
exercise edits your real source, not a copy. Both of those are why
the guardrail exists — read it before you install.
Next: Install — what Learner needs, and the ways in.