How the hook works
The plugin at v0.9.2 runs mneme-hook on three Claude Code events:
- PreToolUse on Edit, Write and MultiEdit. The hook works out the file content the change would produce, then checks the content the change introduces against your recorded decisions. Unrelated lines already in the file don’t trigger a block.
- PreToolUse on Bash. A single command of the form
cat > file <<'EOF'orcat >> file <<'EOF', with a quoted delimiter and nothing else in the command, is checked the same way before it runs. Other shell writes, such asecho >,tee,sed -i, a chainedmkdir -p dir && cat > …, or a script, can’t be reconstructed in advance and pass through. - SessionStart and Stop. At session start the hook records the state of the repository. Each time the agent finishes a turn, it checks every changed text file that git tracks or would track, which catches the shell writes the first check let through. This needs a git work tree.
The exit code semantics are deterministic: exit 0 allows the edit; exit 2 blocks it and returns the violated decision id and rule to the agent as feedback, so it can correct the change without you stepping in. At the end of a turn, a violation keeps the agent working with the same feedback; it doesn’t undo a file that was already written.
on Edit / Write / MultiEdit (and a single cat-heredoc write):
introduced = content the change adds to <path>
result = mneme check --input <introduced content> \
--target-path <path> --query "edit to <path>"
if mode is strict and result is FAIL or WARN:
exit 2 # block, decision id returned to the agent
else:
exit 0 # allow (also when no verdict could be produced)
The how-to guide replays each of these events against mneme-hq 0.9.2, with the recorded output.
Fail-open by design. The hook never blocks on Mneme malfunction. If mneme is not found, an edited file can't be read, or a mneme check call takes longer than 10 seconds, the hook exits 0 and allows the edit. Its notes go to Claude Code’s debug log only, so neither you nor the agent sees them in the session. If mneme-hook itself isn’t on PATH, Claude Code shows a hook error instead.
Install
The Python runtime and the Claude Code plugin are separate artifacts: installing one does not install the other, and no auto-install links them. The plugin is not yet listed in the Claude Code marketplace, so loading it currently requires a checkout of the Mneme repository.
-
Install the runtime
pipx install "mneme-hq>=0.9.2"mneme-hq>=0.9.2keeps the runtime aligned with the current hook contract, including its machine-readable--jsonverdict. -
Scaffold project memory (if you don't have one yet)
mneme initmneme initcreates.mneme/project_memory.jsonwith an empty valid decision corpus — deliberately zero seeded decisions, since every recorded decision is enforceable. It refuses to overwrite an existing corpus unless--forceis supplied. Populate it withmneme add_decision, or bring your existing ADRs in withmneme adr import. -
Load the plugin
git clone --branch v0.9.2 --depth 1 https://github.com/MnemeHQ/mneme claude --plugin-dir ./mneme/integrations/claude-code-pluginThe plugin bundles the enforcement hook, the
mnemeskill, and the namespaced slash commands (/mneme:check,/mneme:context,/mneme:record,/mneme:review). Reload in-session with/reload-pluginsafter changes.A legacy flat installer (
python scripts/install_claude_code.py, with hyphenated/mneme-checkcommands) still works from a git clone, butscripts/is not shipped in the PyPI wheel. -
Verify
Open Claude Code in your project, make an edit that violates a recorded decision, and confirm the hook blocks it and surfaces the decision id.
Slash commands
After installing, four slash commands are available in Claude Code:
| Command | Purpose |
|---|---|
| /mneme:context | Retrieve decisions relevant to your current task |
| /mneme:check | Check a file or draft against project memory |
| /mneme:record | Record a new architectural decision |
| /mneme:review | Audit all pending diff changes against decisions |
Modes
| Mode | Behaviour | Set via |
|---|---|---|
| strict (default) | Block on any non-zero verdict from mneme check |
export MNEME_HOOK_MODE=strict |
| warn | Never block. Claude Code ignores the per-edit reason, so violations reach the agent only from the end-of-turn check | export MNEME_HOOK_MODE=warn |
Warn mode turns blocking off, but it doesn’t show you per-edit findings. To collect findings while you tune new decisions, run mneme check --mode warn yourself or in CI, and keep the hook in strict mode.
Retrieval: what the hook checks and what it misses
Typed FORBID_LITERAL rules (on the paths they apply to), single-term anti-patterns and single-term “no X” constraints are checked on every edit, whatever the file is called. Retrieval only decides which decisions have their multi-term anti-patterns and multi-term “no X” constraints checked. The hook query is "edit to <file_path>", and decisions are scored by overlap between its words (four letters or more, no stemming) and their scope, decision text, constraints, anti-patterns and rationale.
What this means in practice:
- A decision with
scope: storageis retrieved for an edit toapp/storage/layer.py, because “storage” appears in the query. - The same decision isn’t retrieved for an edit to
models.py, so its multi-term rules aren’t checked there. Its typed rules still are. - A generic file name like
utils.pyrarely matches a decision. Retrieval then depends on the words in its directory path and the word “edit” that every hook query contains.
Mitigations:
- Choose scope keywords when recording decisions that match file names in your project. Use
/mneme:recordand follow the scope tip in the command. - Run
/mneme:contextbefore non-trivial edits with a descriptive phrase describing the domain (e.g. "storage layer", "auth middleware"). This uses a richer query than the hook and surfaces decisions the hook might miss. - Run
/mneme:reviewafter a batch of edits to catch violations the per-edit hook missed due to retrieval gaps.
Prefer FORBID_LITERAL rules for decisions that must hold on every file. Use the slash commands for coverage of multi-term rules where file names don’t describe the domain.
Fail-open guarantees
The hook never blocks on execution errors. It exits 0 (allow) when:
mnemeis not found on$PATH- The file an Edit targets can’t be read (a Write to a new file is checked in full)
- A
mneme checkcall times out (limit: 10 s) - Any other OS-level error occurs
Only a real verdict returned by mneme check can cause a block.
Troubleshooting
Hook is not firing
- Confirm
mneme-hookis on$PATH:which mneme-hook(orwhere mneme-hookon Windows). - If not, the pip scripts directory may not be on
$PATH. Add it, or install withpipx. - Confirm Claude Code was started with
--plugin-dirpointing at the plugin. With the legacy installer, confirm.claude/settings.jsoncontains thePreToolUseentry.
Hook fires but nothing is blocked
- Confirm
.mneme/project_memory.jsonexists in the session’s working directory or a parent directory, or setMNEME_MEMORY. With no memory file the hook allows every change silently. - Check your decision scope keywords against the file names being edited — see the retrieval section above.
- Confirm neither
MNEME_HOOK_MODEnor the plugin’smodeoption is set towarn, which never blocks. - Check Claude Code’s debug log for notes from
mneme-hook, such as “Failing open”.
Too many false positives / blocks
- Narrow the rule with
include_paths, or check candidate rules withmneme check --mode warnbefore you import them. - Review the triggered anti-patterns with
/mneme:contextto see if they're too broad.
FAQ
How is the Mneme hook different from CLAUDE.md?
Does the hook slow down Claude Code sessions?
mneme check. In our test on Windows with mneme-hq 0.9.2, a hook call took about a second. There is no network round trip, embedding model or vector store; the check is deterministic keyword matching.What happens if Mneme itself fails?
mneme check --mode strict) in CI is where hard-blocking lives; the editor-time hook prioritizes availability.