TL;DR: on each upgrade, the installer re-injects the memtrace UserPromptSubmit and PostToolUse hooks into ~/.claude/settings.json at a version-pinned package-store path without removing the previous version's entry. They accumulate (three copies each after two upgrades, at three different paths). Once an old version's store directory is pruned, that entry becomes a dangling hook whose command path no longer exists, so it fails on every matching event. The v0.6.30 transparent-install work (#25) fixed the install footprint and the uninstall, but not this dedup-on-inject axis.
This surfaced while comparing my ~/.claude/settings.json before and after upgrades on machines I use regularly, across three releases, not a synthetic case.
Environment
- memtrace 0.6.30 (the residual was also produced by 0.6.20)
- Claude Code 2.1.175
- macOS, Apple Silicon
What I observed (real config)
After upgrading across 0.6.18, 0.6.20, 0.6.30, ~/.claude/settings.json carried three copies each of the two memtrace hooks:
UserPromptSubmit: userprompt-claude.sh at the 0.6.20 store path, the 0.6.30 store path, and the npm-global path.
PostToolUse: posttool-mcp-telemetry.sh at those same three paths.
The 0.6.20 store path no longer exists on disk (pruned), so that entry is a dangling hook and errors on every UserPromptSubmit / PostToolUse.
Repro
- Note the memtrace hook entries in
~/.claude/settings.json.
- Upgrade memtrace (npm global, or
memtrace install).
- Diff
~/.claude/settings.json: the memtrace hooks are appended again at the new version path; the previous entry is not removed.
- Remove or let the old version's store directory be pruned; the stale hook's command path is now missing.
Impact
Suggested fix
- Dedup on inject: match an existing memtrace hook by a stable identity (script basename, or a managed marker comment) and replace it in place rather than append.
- Or use a version-stable command path (the
memtrace CLI shim) instead of a version-pinned store path, so re-injection is idempotent and never dangles.
Separately, and for the record: a user-level ~/.claude/settings.local.json is not read by Claude Code (only the project-level .claude/settings.local.json is), so keeping the user-global hooks in ~/.claude/settings.json, as the installer does, is the correct surface.
TL;DR: on each upgrade, the installer re-injects the memtrace
UserPromptSubmitandPostToolUsehooks into~/.claude/settings.jsonat a version-pinned package-store path without removing the previous version's entry. They accumulate (three copies each after two upgrades, at three different paths). Once an old version's store directory is pruned, that entry becomes a dangling hook whose command path no longer exists, so it fails on every matching event. The v0.6.30 transparent-install work (#25) fixed the install footprint and the uninstall, but not this dedup-on-inject axis.This surfaced while comparing my
~/.claude/settings.jsonbefore and after upgrades on machines I use regularly, across three releases, not a synthetic case.Environment
What I observed (real config)
After upgrading across 0.6.18, 0.6.20, 0.6.30,
~/.claude/settings.jsoncarried three copies each of the two memtrace hooks:UserPromptSubmit:userprompt-claude.shat the 0.6.20 store path, the 0.6.30 store path, and the npm-global path.PostToolUse:posttool-mcp-telemetry.shat those same three paths.The 0.6.20 store path no longer exists on disk (pruned), so that entry is a dangling hook and errors on every
UserPromptSubmit/PostToolUse.Repro
~/.claude/settings.json.memtrace install).~/.claude/settings.json: the memtrace hooks are appended again at the new version path; the previous entry is not removed.Impact
~/.claude/settings.jsonon every upgrade, which is the external-config-management problem from memtrace install mutates user-global Claude Code config (~/.claude/) with no consent prompt, no documented mutation list, no opt-out, and a non-restoring uninstall #25 (chezmoi, Nix home-manager) recurring through a different door.Suggested fix
memtraceCLI shim) instead of a version-pinned store path, so re-injection is idempotent and never dangles.Separately, and for the record: a user-level
~/.claude/settings.local.jsonis not read by Claude Code (only the project-level.claude/settings.local.jsonis), so keeping the user-global hooks in~/.claude/settings.json, as the installer does, is the correct surface.