Summary
The Claude Code adapter generates hook commands using relative script paths (node .claude/hooks/...). Claude Code runs hooks with the directory the CLI was launched from as the working directory — not the user's home. So whenever claude is started from any folder other than the config root (e.g. another drive or a project dir), Node cannot resolve the script and the hook crashes with Cannot find module. On the Stop event this surfaces to the user as a Stop hook error.
A second, related problem: detectPlatform() misidentifies Claude Code as Cursor when both ~/.cursor and ~/.claude exist.
Tested with @evomap/evolver@1.89.17 on Windows 11, Claude Code.
Bug 1 — relative hook paths break outside the config root
src/adapters/claudeCode.js:
function buildClaudeHooks(evolverRoot) {
const scriptsBase = '.claude/hooks'; // <-- relative
...
command: `node ${scriptsBase}/evolver-session-start.js`,
This produces settings.json entries like node .claude/hooks/evolver-session-end.js. Claude Code executes hooks with cwd = the directory the user launched claude in, so the relative path only resolves when that happens to be the config root (home).
Reproduction
$ cd /d # any dir without a ./.claude/hooks
$ echo '{}' | node .claude/hooks/evolver-session-end.js
node:internal/modules/cjs/loader:1386
throw err;
^
Error: Cannot find module 'D:\.claude\hooks\evolver-session-end.js'
In a real session the Stop hook then reports:
Stop hook error: Failed with non-blocking status code: node:internal/modules/cjs/loader:1386
throw err;
Suggested fix
Emit an absolute path. install() already knows configRoot, so thread it into buildClaudeHooks and build the path with it:
function buildClaudeHooks(evolverRoot, configRoot) {
const scriptsBase = path.join(configRoot, '.claude', 'hooks');
...
command: `node "${path.join(scriptsBase, 'evolver-session-start.js')}"`,
(Quote the path so a config root containing spaces still works.) The statusLine command in a typical Claude Code config already uses an absolute path, so this is consistent with the platform's own conventions.
Bug 2 — Claude Code misdetected as Cursor
src/adapters/hookAdapter.js:
const PLATFORMS = {
cursor: { ... detector: '.cursor' },
'claude-code': { ... detector: '.claude' },
...
};
function detectPlatform(cwd) {
...
for (const [id, meta] of Object.entries(PLATFORMS)) {
if (fs.existsSync(path.join(home, meta.detector))) return id; // first match wins
}
}
When ~/.cursor and ~/.claude both exist (common — many users have both editors, or a previous run created ~/.cursor), iteration order makes cursor win, so setup-hooks writes to ~/.cursor/hooks even inside a Claude Code session. The hooks then silently don't run under Claude Code.
Suggested fix
Prefer an explicit runtime signal over directory presence. Claude Code sets CLAUDECODE=1 (and CLAUDE_CODE_ENTRYPOINT); Cursor sets CURSOR_TRACE_ID / TERM_PROGRAM=cursor. Check those env vars first, then fall back to directory detection. At minimum, document that --platform=claude-code is required when multiple config dirs coexist.
Environment
@evomap/evolver 1.89.17
- OS: Windows 11
- Host: Claude Code
- Node: (relevant) launched from a non-home working directory
Summary
The Claude Code adapter generates hook commands using relative script paths (
node .claude/hooks/...). Claude Code runs hooks with the directory the CLI was launched from as the working directory — not the user's home. So wheneverclaudeis started from any folder other than the config root (e.g. another drive or a project dir), Node cannot resolve the script and the hook crashes withCannot find module. On theStopevent this surfaces to the user as aStop hook error.A second, related problem:
detectPlatform()misidentifies Claude Code as Cursor when both~/.cursorand~/.claudeexist.Tested with
@evomap/evolver@1.89.17on Windows 11, Claude Code.Bug 1 — relative hook paths break outside the config root
src/adapters/claudeCode.js:This produces
settings.jsonentries likenode .claude/hooks/evolver-session-end.js. Claude Code executes hooks withcwd= the directory the user launchedclaudein, so the relative path only resolves when that happens to be the config root (home).Reproduction
In a real session the
Stophook then reports:Suggested fix
Emit an absolute path.
install()already knowsconfigRoot, so thread it intobuildClaudeHooksand build the path with it:(Quote the path so a config root containing spaces still works.) The
statusLinecommand in a typical Claude Code config already uses an absolute path, so this is consistent with the platform's own conventions.Bug 2 — Claude Code misdetected as Cursor
src/adapters/hookAdapter.js:When
~/.cursorand~/.claudeboth exist (common — many users have both editors, or a previous run created~/.cursor), iteration order makescursorwin, sosetup-hookswrites to~/.cursor/hookseven inside a Claude Code session. The hooks then silently don't run under Claude Code.Suggested fix
Prefer an explicit runtime signal over directory presence. Claude Code sets
CLAUDECODE=1(andCLAUDE_CODE_ENTRYPOINT); Cursor setsCURSOR_TRACE_ID/TERM_PROGRAM=cursor. Check those env vars first, then fall back to directory detection. At minimum, document that--platform=claude-codeis required when multiple config dirs coexist.Environment
@evomap/evolver1.89.17