Skip to content

fix(claude-code): resolve evolver hooks from project OR home scope#592

Open
Bala-Shunmugam-M wants to merge 1 commit into
EvoMap:mainfrom
Bala-Shunmugam-M:fix/claude-hook-project-or-home-fallback
Open

fix(claude-code): resolve evolver hooks from project OR home scope#592
Bala-Shunmugam-M wants to merge 1 commit into
EvoMap:mainfrom
Bala-Shunmugam-M:fix/claude-hook-project-or-home-fallback

Conversation

@Bala-Shunmugam-M

@Bala-Shunmugam-M Bala-Shunmugam-M commented Jun 27, 2026

Copy link
Copy Markdown

Problem

buildClaudeHooks() in src/adapters/claudeCode.js emits 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_managed settings in ~/.claude/settings.json — every project except the install root resolves .claude/hooks/evolver-*.js to a non-existent path.

The hooks then fail silently: their 2–8 ms timeout values 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:

  • win32: cmd /c "IF EXIST "%CLAUDE_PROJECT_DIR%\.claude\hooks\X" (node "%CLAUDE_PROJECT_DIR%\.claude\hooks\X") 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"'

Tries the project-local copy first (so per-project installs and the copyHookScripts path 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-*.js script names, so isEvolverHookCommand() keeps matching them and uninstall() / merge dedup are unaffected.

Test

Adds buildClaudeHooks commands resolve from project OR home scope to test/adapters.test.js, asserting every emitted command (1) references the project scope, (2) falls back to the home scope, (3) invokes an evolver-*.js script, and (4) stays recognizable to the uninstall matcher.

Full suite green locally (node --test): 56 passed, 0 failed.

Notes

  • No behavior change for an already-correct per-project install — the IF EXIST / [ -f ] branch picks the project copy first.
  • Cross-platform; process.platform selects 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-*.js against 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 new buildHookCommand() (Windows cmd + IF EXIST / %USERPROFILE%, POSIX sh with $CLAUDE_PROJECT_DIR then $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 by isEvolverHookCommand() for uninstall/merge.

Reviewed by Cursor Bugbot for commit 1bb856b. Bugbot is set up for automated code reviews on this repo. Configure here.

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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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}")"`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1bb856b. Configure here.

@imxyanua

Copy link
Copy Markdown
Contributor

Hi @Bala-Shunmugam-M,

Thanks for this, and for the precise root-cause analysis. The bug is real and confirmed: a bare relative node path such as .claude/hooks/evolver-*.js resolves relative to the project directory, which means a home/global installation never fires its hooks outside the installation root. Because the hook timeouts are very short, the failure is effectively silent.

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 .claude/hooks copy when present, and falls back to the home installation otherwise. That is a legitimate behavioral enhancement and worth evaluating independently of the underlying bug fix.

A couple of notes if the project-precedence behavior is considered:

  • The shell command only interpolates host-provided environment variables (CLAUDE_PROJECT_DIR, USERPROFILE, HOME) and fixed script names, so there is no untrusted input involved. That part looks good.
  • It would be worth making an explicit product decision on whether a project-local hook copy should take precedence over a home installation, since that represents a behavioral choice rather than a bug fix.

Either way, thank you for the careful diagnosis and the cross-platform implementation. We'll reconcile this approach with the incoming fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants