Standalone CLI: copyHookScript() double-joins 'dist', so hook script is never copied but CLI logs false success
Summary
When running the standalone CLI (node dist/cli.js), hook installation writes working hook entries into ~/.claude/settings.json, but the referenced hook script is never actually copied to ~/.pixel-agents/hooks/claude-hook.js — because copyHookScript() is called with the wrong base path, doubling the dist segment. Every Claude Code hook event on the machine then fails silently (node: cannot find module) until the file is manually placed, and the CLI logs a false "Hooks installed" success right after logging the actual failure.
Steps to reproduce
git clone https://github.com/pixel-agents-hq/pixel-agents.git
cd pixel-agents
npm install
npm run build
node dist/cli.js --port 3100Observed log output
[Pixel Agents] Hooks installed in ~/.claude/settings.json
[Pixel Agents] Hook script not found at /path/to/pixel-agents/dist/dist/hooks/claude-hook.js
[Pixel Agents] Hooks installedNote the double dist/dist in the "not found" path, and that the CLI reports success (Hooks installed) immediately after reporting the failure.
$ ls ~/.pixel-agents/hooks/
# empty
$ cat ~/.claude/settings.json | jq '.hooks.Stop'
[
{
"matcher": "",
"hooks": [{ "type": "command", "command": "node \"/home/USER/.pixel-agents/hooks/claude-hook.js\"", "timeout": 5 }]
}
]So every hook event fired by Claude Code (globally, for every project) invokes a script that doesn't exist.
Root cause
server/src/cli.ts:
const distRoot = __dirname; // already == .../dist (line 63)
...
copyHookScript(distRoot); // lines 110, 138server/src/providers/hook/claude/claudeHookInstaller.ts:
export function copyHookScript(extensionPath: string): void {
const src = path.join(extensionPath, 'dist', 'hooks', CLAUDE_HOOK_SCRIPT_NAME); // line 172
...copyHookScript assumes its argument is the package/extension root and appends dist/hooks/... itself — correct for the VS Code adapter, which calls it with this.context.extensionPath (the extension root, not including dist/) in adapters/vscode/PixelAgentsViewProvider.ts. But cli.ts passes distRoot, which is __dirname of the already-compiled dist/cli.js — i.e. already inside dist/. The result is a lookup at <root>/dist/dist/hooks/claude-hook.js, which never exists, so the fs.existsSync(src) check in copyHookScript fails and it console.warns and returns without copying — while the caller in cli.ts has no way to detect that failure and unconditionally logs "[Pixel Agents] Hooks installed" right after.
Expected behavior
copyHookScriptshould resolve the hook script relative to the actualdist/hooks/next tocli.js, e.g.copyHookScript(path.dirname(distRoot))fromcli.ts(undoing the extradistjoin), or better: givecopyHookScriptan explicit "already the dist dir" mode, since it's now called from two different path conventions (extension root vs. compiled-dist root).- The CLI should not print "Hooks installed" (or should print a warning) when
copyHookScriptfailed to find/copy the source file —copyHookScriptcurrently swallows this asconsole.warnwith no return value the caller can check.
Impact
This silently breaks all Claude Code hooks (SessionStart, Stop, PreToolUse, PostToolUse, Notification, etc.) machine-wide for anyone who runs the standalone CLI from a source build, with no visible error beyond a log line that's easy to miss between two lines both saying "installed".
Suggested fix
// claudeHookInstaller.ts
export function copyHookScript(hookScriptsSourceDir: string): boolean {
const src = path.join(hookScriptsSourceDir, CLAUDE_HOOK_SCRIPT_NAME);
...
if (!fs.existsSync(src)) {
console.warn(`[Pixel Agents] Hook script not found at ${src}`);
return false;
}
fs.copyFileSync(src, dst);
fs.chmodSync(dst, 0o700);
return true;
}// cli.ts
const ok = copyHookScript(path.join(distRoot, 'hooks'));
console.log(ok ? '[Pixel Agents] Hooks installed' : '[Pixel Agents] Hooks NOT installed — hook script missing');// PixelAgentsViewProvider.ts
copyHookScript(path.join(this.context.extensionPath, 'dist', 'hooks'));(i.e. make the parameter always mean "directory containing the hook script", removing the ambiguity between the two call sites.)
Environment
- OS: Ubuntu 24.04.4 LTS
- Node: v22.0.0
- Built from source at current
main
Source: pixel-agents-hq/pixel-agents