Summary
Project-local is mnemon's documented default — per the README: "mnemon setup defaults to local (project-scoped .claude/), recommended for most users." This report is not about that default; it's about one degenerate case where it misfires:
When mnemon setup --target claude-code runs with cwd == $HOME, the "project-local" ./.claude/ is ~/.claude/ — Claude Code's user-global config directory. The relative hook commands setup writes (.claude/hooks/mnemon/prime.sh, ...) land in the user-global settings.json, which loads for every session on the machine — but the relative paths only resolve when a session's working directory happens to be $HOME. From any other directory, interactive sessions log
Stop hook error: Failed with non-blocking status code: /bin/sh: 1: .claude/hooks/mnemon/stop.sh: not found
on every turn, headless (-p) sessions fail silently, and the memory integration quietly stops working (no priming, no recall, no remember).
Why cwd==$HOME is common rather than exotic: installs are increasingly agent-mediated — a user points a Claude Code session at the README and the agent runs the command from wherever it sits. Non-interactive remote installs land there 100% of the time: ssh host 'mnemon setup --target claude-code --yes' always runs with cwd=$HOME.
Reproduction
Version-stamped: mnemon 0.1.13, Claude Code CLI 2.1.163, reproduced on macOS (arm64) and Debian Linux (x86_64).
cd ~ && mnemon setup --target claude-code --yes # documented default (project-local)
cd /tmp && claude # any session outside $HOME
# interactive session → "stop.sh: not found" every turn (observed in production; see Evidence)
# claude -p from the same dir → no error shown; hooks simply never run
Evidence (all observed, with controls)
Write-side (mnemon 0.1.13, binary invoked directly — no agent in the loop):
| setup invoked from |
lands in |
command form |
outcome |
$HOME (default scope) |
~/.claude/settings.json (user-global) |
relative |
broken outside $HOME |
| genuine project dir (default scope) |
<proj>/.claude/settings.json |
relative |
works (see loader note) |
anywhere with --global |
~/.claude/settings.json |
absolute |
works everywhere (control) |
Loader-side (Claude Code 2.1.163, headless -p, sandboxed HOME):
- Project
settings.json loads only when the session cwd is the project dir (no parent/git-root fallback observed) — exactly where the relative path resolves. So genuine project-local installs are unaffected; the relative form is even the portable choice there (survives repo clones, unlike an absolute path).
- The user-global file loads for every session regardless of cwd; a relative command there resolved only from
$HOME. An absolute command in the same file ran from every cwd tested (control).
- Stated as version-observed behavior, not a contract.
Production instance (before/after): on a server where an agent had run the README install from /root: three relative entries in /root/.claude/settings.json, every interactive session logging the not-found errors (transcripts retained). Re-running mnemon setup --target claude-code --yes --global repaired in place — relative entries replaced (not duplicated), hooks verified firing from /tmp afterward.
Frequency: on a personal fleet of 8 machines (a 9th is client-managed and not used with Claude — excluded), 6 had mnemon installed. 2 of those 6 had landed in this broken form, both since repaired — one is the production before/after above.
Proposed minimal fix (PR attached)
A collision guard in ClaudeRegisterHooks: if the project-local config dir resolves to the same directory as Claude Code's user-global config dir (symlink-resolved comparison on both sides; honors CLAUDE_CONFIG_DIR), write absolute hook commands for exactly that case and print a note suggesting --global for explicitness. This honors the user-global file's existing contract (your --global path already writes absolute commands) without touching the project-local default — genuine project-local installs keep their current relative form, byte-for-byte.
A stricter alternative if you prefer it: hard-error on the collision ("this is ~/.claude; use --global"). Most explicit, and agents handle instructive errors well — but it makes --yes automation exit nonzero. The attached PR implements reroute+note; happy to flip it to the error form, it's a small change and both behaviors are covered by tests.
Related observations (no PR, separate from the bug)
- Setup run in a subdirectory of a project places
.claude/ where (per the loader behavior above) sessions started at the project root never load it — a silent no-op install. A one-line end-of-setup note ("installed to <dir>/.claude; loads for sessions started in <dir>") would surface both this and the $HOME case without changing any behavior. Happy to follow up if useful.
- More generally: agent-mediated installs run from arbitrary working directories (the fleet numbers above). If you'd like the non-interactive path to behave differently in light of that, glad to implement whatever direction you choose — no change proposed here; the documented project-local default is respected as-is.
Summary
Project-local is mnemon's documented default — per the README: "
mnemon setupdefaults to local (project-scoped.claude/), recommended for most users." This report is not about that default; it's about one degenerate case where it misfires:When
mnemon setup --target claude-coderuns with cwd ==$HOME, the "project-local"./.claude/is~/.claude/— Claude Code's user-global config directory. The relative hook commands setup writes (.claude/hooks/mnemon/prime.sh, ...) land in the user-globalsettings.json, which loads for every session on the machine — but the relative paths only resolve when a session's working directory happens to be$HOME. From any other directory, interactive sessions logon every turn, headless (
-p) sessions fail silently, and the memory integration quietly stops working (no priming, no recall, no remember).Why cwd==
$HOMEis common rather than exotic: installs are increasingly agent-mediated — a user points a Claude Code session at the README and the agent runs the command from wherever it sits. Non-interactive remote installs land there 100% of the time:ssh host 'mnemon setup --target claude-code --yes'always runs with cwd=$HOME.Reproduction
Version-stamped: mnemon 0.1.13, Claude Code CLI 2.1.163, reproduced on macOS (arm64) and Debian Linux (x86_64).
Evidence (all observed, with controls)
Write-side (mnemon 0.1.13, binary invoked directly — no agent in the loop):
$HOME(default scope)~/.claude/settings.json(user-global)$HOME<proj>/.claude/settings.json--global~/.claude/settings.jsonLoader-side (Claude Code 2.1.163, headless
-p, sandboxedHOME):settings.jsonloads only when the session cwd is the project dir (no parent/git-root fallback observed) — exactly where the relative path resolves. So genuine project-local installs are unaffected; the relative form is even the portable choice there (survives repo clones, unlike an absolute path).$HOME. An absolute command in the same file ran from every cwd tested (control).Production instance (before/after): on a server where an agent had run the README install from
/root: three relative entries in/root/.claude/settings.json, every interactive session logging the not-found errors (transcripts retained). Re-runningmnemon setup --target claude-code --yes --globalrepaired in place — relative entries replaced (not duplicated), hooks verified firing from/tmpafterward.Frequency: on a personal fleet of 8 machines (a 9th is client-managed and not used with Claude — excluded), 6 had mnemon installed. 2 of those 6 had landed in this broken form, both since repaired — one is the production before/after above.
Proposed minimal fix (PR attached)
A collision guard in
ClaudeRegisterHooks: if the project-local config dir resolves to the same directory as Claude Code's user-global config dir (symlink-resolved comparison on both sides; honorsCLAUDE_CONFIG_DIR), write absolute hook commands for exactly that case and print a note suggesting--globalfor explicitness. This honors the user-global file's existing contract (your--globalpath already writes absolute commands) without touching the project-local default — genuine project-local installs keep their current relative form, byte-for-byte.A stricter alternative if you prefer it: hard-error on the collision ("this is
~/.claude; use--global"). Most explicit, and agents handle instructive errors well — but it makes--yesautomation exit nonzero. The attached PR implements reroute+note; happy to flip it to the error form, it's a small change and both behaviors are covered by tests.Related observations (no PR, separate from the bug)
.claude/where (per the loader behavior above) sessions started at the project root never load it — a silent no-op install. A one-line end-of-setup note ("installed to<dir>/.claude; loads for sessions started in<dir>") would surface both this and the$HOMEcase without changing any behavior. Happy to follow up if useful.