Safety

What a fill exercise does to your files, what stops it leaving them broken, and how to remove Learner entirely.

The guardrail

A fill exercise deliberately breaks a file you care about, and a session can end at any moment: a crash, a closed terminal, /clear, a meeting. So the Stop hook carries a second job. While a leftover marker survives, it re-blocks the end of the session and asks for the file to be put back before anything else happens:

the guardrail, blocking
🎓 Learner — an unfinished fill-in exercise left // LEARNER-TODO markers in: hooks/learner-config.sh
Before anything else: restore the correct implementation, remove every // LEARNER-TODO it left, and verify it compiles/lints/tests. Do not finish while a marker remains.

Four properties, as they are actually implemented in hooks/learner-quiz.sh:

Leftovers only
A file counts when the working tree has a marker in it and HEAD does not. The unit is the file rather than the individual marker: the hook lists the files matching in the working tree, lists the files matching in HEAD, and takes the difference. A marker you committed on purpose is therefore repo content and never triggers it — which is what lets this project write the string in its README, its tests and this very page. In a repository with no commits yet there is no HEAD to subtract from, so every marker in the tree counts, which is the right answer there.
Untracked files count
The search runs with --untracked. A file the session created a moment ago is the commonest place for a hole to be left behind, and it is precisely the file git would otherwise say nothing about.
It fires even when the quiz is off
The guardrail runs before the activation check, so neither "enabled": false nor a missing or invalid level silences it: an abandoned exercise still has to be cleaned up. Exactly four things stop it — no jq, no session id in the hook payload, not being inside a git repository, or the repository sitting under disabledPaths. That last one is the deliberate hole: a repository you told Learner to leave alone is left alone, holes included, so if you start an exercise there by hand nothing will chase you about it.
Bounded, so a session can always end
At most two blocks per outstanding exercise, counted in a per-session file under $TMPDIR. After the second, the hook goes quiet rather than hang a session on a marker Claude cannot or will not clear — the tree is still broken, but that is your call to make and not a loop you have to escape. Cleaning the tree deletes the counter, so a later exercise starts again with a fresh budget of two. When it does block, it names up to twenty files and says how many more there are.

Worth stating plainly what all of this is not: the guardrail is a check that runs when a session ends, not a sandbox. It is the reason an interrupted exercise gets noticed instead of shipped, and the reason fill exercises are confined to files inside the repository — an edit outside it is never recorded as quiz material, so a hole can never be cut where a repo-scoped check could not see it. Committing before you start an exercise still costs you nothing, and git will show you every hole.

What counts as quiz material

An exclusion list, not an include list. Every file Claude edits inside the repository is material — an edit outside it, a dotfile in $HOME or a file in another project, is never recorded — minus a built-in floor you cannot configure away:

Then minus your own untrackGlobs on top of that floor. The globs are matched with shell pattern matching and split on whitespace internally, so a glob containing a space is not supported.

Uninstall

./uninstall.sh                            # hooks, skill and wiring; data stays
./uninstall.sh --purge                    # also delete learner.json and learner/
./uninstall.sh --project /path/to/repo    # clean up a legacy per-project install

The plain form is the exact reverse of the install: the six hook files, the skill, and every learner entry in settings.json, dropping any hook event left empty. Your config and your progress survive, so a reinstall picks up where you left off. --purge deletes those too.

--project is for anyone who installed an earlier, per-repository version of Learner: it removes the hooks, the skill, the .claude/settings.json entries and the .gitignore lines from inside one specific repository. Per-repo overrides are not centrally enumerable, so a plain ./uninstall.sh cannot find them for you — delete a stray learner.local.json by hand, or point --project at the repo.

Installed via Homebrew or apt? Run learner-uninstall instead of ./uninstall.sh — same flags, learner-uninstall --purge included. sudo apt remove learner / brew uninstall learner only remove learner-install and learner-uninstall themselves; neither touches ~/.claude. Run learner-uninstall first if you want that cleaned up too — do it before the package step if you can, since afterward the binary that does it is gone.

Both forms refuse to start if settings.json cannot be parsed, and say so, rather than getting half way and leaving the wiring pointing at hooks that no longer exist.

Next: what Learner is, if you came in sideways.