Problem
When install.sh runs on a machine that already has hook entries pointing at an older install path (e.g. the legacy ~/.tinynudge/notify.sh from before the rename, or any prior ~/.stack-nudge/notify.sh entry that's been moved/edited), the installer appends new entries instead of replacing the existing ones.
After upgrade, ~/.claude/settings.json ends up with two Stop hook blocks and two PermissionRequest hook blocks: one stale, one fresh. Both fire on every event. The stale one points at a path that no longer exists once the user removes the old install, so every Stop and PermissionRequest then logs a "no such file or directory" error.
Repro
- Have an older config wired up:
"Stop": [
{ "matcher": "", "hooks": [{ "type": "command", "command": "/Users/me/.tinynudge/notify.sh claude-code stop" }] }
]
- Run
./install.sh from the current repo.
- Inspect
~/.claude/settings.json — the new ~/.stack-nudge/notify.sh block has been appended alongside the old one rather than replacing it.
Same issue applies to ~/.cursor/hooks.json.
Expected
The installer should detect any existing hook entry whose command path looks like a prior stack-nudge / tinynudge notify.sh invocation (matching */notify.sh claude-code <event> or similar) and replace it in-place, rather than appending.
Suggested fix
In the Python snippets at install.sh:167 and install.sh:199 that mutate the JSON config, before appending the new hook block, walk the existing hooks[event] list and drop any entry whose nested hooks[*].command matches a regex like:
.*/(tinynudge|stack-nudge)/notify\.sh\b
Then append the fresh entry. That keeps non-stack-nudge hooks (e.g. pixel-agents, custom user hooks) untouched while idempotently replacing our own.
Why this matters
- Silent failures: the dead hook command runs on every Stop/PermissionRequest and exits non-zero.
- Surprises users who don't notice duplicates until they
cat the file or notice extra noise.
./uninstall.sh likely has the same blind spot — worth a look there too.
Related
Filed during the same install session as #11.
Problem
When
install.shruns on a machine that already has hook entries pointing at an older install path (e.g. the legacy~/.tinynudge/notify.shfrom before the rename, or any prior~/.stack-nudge/notify.shentry that's been moved/edited), the installer appends new entries instead of replacing the existing ones.After upgrade,
~/.claude/settings.jsonends up with twoStophook blocks and twoPermissionRequesthook blocks: one stale, one fresh. Both fire on every event. The stale one points at a path that no longer exists once the user removes the old install, so every Stop and PermissionRequest then logs a "no such file or directory" error.Repro
./install.shfrom the current repo.~/.claude/settings.json— the new~/.stack-nudge/notify.shblock has been appended alongside the old one rather than replacing it.Same issue applies to
~/.cursor/hooks.json.Expected
The installer should detect any existing hook entry whose command path looks like a prior stack-nudge / tinynudge
notify.shinvocation (matching*/notify.sh claude-code <event>or similar) and replace it in-place, rather than appending.Suggested fix
In the Python snippets at
install.sh:167andinstall.sh:199that mutate the JSON config, before appending the new hook block, walk the existinghooks[event]list and drop any entry whose nestedhooks[*].commandmatches a regex like:Then append the fresh entry. That keeps non-stack-nudge hooks (e.g. pixel-agents, custom user hooks) untouched while idempotently replacing our own.
Why this matters
catthe file or notice extra noise../uninstall.shlikely has the same blind spot — worth a look there too.Related
Filed during the same install session as #11.