fix(claude-code): resolve evolver hooks from project OR home scope#592
fix(claude-code): resolve evolver hooks from project OR home scope#592Bala-Shunmugam-M wants to merge 1 commit into
Conversation
buildClaudeHooks emitted bare `node .claude/hooks/evolver-*.js` commands. Claude Code resolves a hook command's relative path against each *project* directory, not the evolver install root. So when evolver is installed once at the home/global scope (hooks in ~/.claude/hooks, settings in ~/.claude/ settings.json), every project except the install root resolves `.claude/hooks/evolver-*.js` to a non-existent path and the hooks fail silently (their 2-8ms timeouts swallow the error) — session memory injection, signal detection, and outcome recording never run. Emit a project-or-home fallback command instead, cross-platform: - win32: cmd /c "IF EXIST %CLAUDE_PROJECT_DIR%\.claude\hooks\X (node project) ELSE (node %USERPROFILE%\.claude\hooks\X)" - posix: sh -c 'f="$CLAUDE_PROJECT_DIR/.claude/hooks/X"; [ -f "$f" ] || f="$HOME/.claude/hooks/X"; node "$f"' Commands still contain the evolver-*.js script names, so isEvolverHookCommand keeps recognizing them and uninstall/merge is unaffected. Adds a regression test asserting every emitted command tries the project scope, falls back to home, and stays recognizable to the uninstall matcher. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1bb856b. Configure here.
| if (process.platform === 'win32') { | ||
| const proj = `%CLAUDE_PROJECT_DIR%\\.claude\\hooks\\${scriptName}`; | ||
| const home = `%USERPROFILE%\\.claude\\hooks\\${scriptName}`; | ||
| return `cmd /c "IF EXIST "${proj}" (node "${proj}") ELSE (node "${home}")"`; |
There was a problem hiding this comment.
Windows hook cmd quoting broken
High Severity
The win32 branch of buildHookCommand uses nested double quotes within the cmd /c "..." command. cmd.exe misinterprets the quote after IF EXIST as closing the outer command, which causes the project-or-home fallback logic to parse incorrectly and fail silently for global Windows installs.
Reviewed by Cursor Bugbot for commit 1bb856b. Configure here.
|
Thanks for this, and for the precise root-cause analysis. The bug is real and confirmed: a bare relative node path such as A quick heads-up so you don't spend more time than necessary: a fix for the core relative-path resolution issue is already queued and will land in an upcoming release. The planned change resolves the emitted hook script path to an absolute path rather than a relative one. As a result, this PR will overlap with that fix and would likely conflict once it lands. That said, your PR introduces one idea that the queued fix does not: the project-first, then home fallback behavior. The queued fix pins execution to the installation scope, meaning a home installation always uses the home-installed scripts. Your approach instead prefers a project-local A couple of notes if the project-precedence behavior is considered:
Either way, thank you for the careful diagnosis and the cross-platform implementation. We'll reconcile this approach with the incoming fix. |


Problem
buildClaudeHooks()insrc/adapters/claudeCode.jsemits bare relative commands:command: `node .claude/hooks/evolver-session-start.js`Claude Code resolves a hook command's relative path against the project directory (
%CLAUDE_PROJECT_DIR%), not the evolver install root. When evolver is installed once at the home/global scope — hooks in~/.claude/hooks,_evolver_managedsettings in~/.claude/settings.json— every project except the install root resolves.claude/hooks/evolver-*.jsto a non-existent path.The hooks then fail silently: their 2–8 ms
timeoutvalues mean the host swallows the error. Session-start memory injection, signal detection, task recall, and outcome recording never run in those projects, with no visible error. (Observed in the wild on a Windows global install: hooks present in~/.claude/hooks, but a project on another drive never fired any of them.)Fix
Emit a project-or-home fallback command, cross-platform:
cmd /c "IF EXIST "%CLAUDE_PROJECT_DIR%\.claude\hooks\X" (node "%CLAUDE_PROJECT_DIR%\.claude\hooks\X") ELSE (node "%USERPROFILE%\.claude\hooks\X")"sh -c 'f="$CLAUDE_PROJECT_DIR/.claude/hooks/X"; [ -f "$f" ] || f="$HOME/.claude/hooks/X"; node "$f"'Tries the project-local copy first (so per-project installs and the
copyHookScriptspath keep working), then falls back to the home install — so the hook fires no matter which scope it was installed at.The emitted commands still contain the
evolver-*.jsscript names, soisEvolverHookCommand()keeps matching them anduninstall()/ merge dedup are unaffected.Test
Adds
buildClaudeHooks commands resolve from project OR home scopetotest/adapters.test.js, asserting every emitted command (1) references the project scope, (2) falls back to the home scope, (3) invokes anevolver-*.jsscript, and (4) stays recognizable to the uninstall matcher.Full suite green locally (
node --test): 56 passed, 0 failed.Notes
IF EXIST/[ -f ]branch picks the project copy first.process.platformselects the shell form.Note
Low Risk
Hook install path resolution only; uninstall matching unchanged and per-project installs still prefer local scripts.
Overview
Fixes silent hook failures when evolver is installed at home/global scope: Claude Code used to resolve bare
node .claude/hooks/evolver-*.jsagainst each project directory, so session memory, signal detection, task recall, and session-end recording never ran outside the install root.buildClaudeHooks()now emits project-then-home shell commands via newbuildHookCommand()(Windowscmd+IF EXIST/%USERPROFILE%, POSIXshwith$CLAUDE_PROJECT_DIRthen$HOME). Per-project installs are unchanged because the project copy is tried first.A regression test asserts every emitted command references both scopes, still invokes
evolver-*.js, and remains matched byisEvolverHookCommand()for uninstall/merge.Reviewed by Cursor Bugbot for commit 1bb856b. Bugbot is set up for automated code reviews on this repo. Configure here.