From b50aa6d4b3125251592e5f2b6f27068eb652ad03 Mon Sep 17 00:00:00 2001 From: Dmitrii Malakhov Date: Wed, 15 Apr 2026 10:49:32 +0200 Subject: [PATCH] fix: isolate hooks as CommonJS so they survive ESM parent package.json When ~/.claude/package.json (or any ancestor) contains "type": "module", Node treats every .js file under that tree as an ES module. The caveman hooks use require() and crash with: ReferenceError: require is not defined in ES module scope surfaced as: SessionStart:clear hook error / UserPromptSubmit hook error Failed with non-blocking status code: .../caveman-activate.js:9 This pins the hooks directory to CommonJS via a local package.json, so module resolution no longer depends on whatever the user's ~/.claude directory declares. Also wires the new file into install/uninstall scripts so standalone installs (curl | bash / Invoke-WebRequest) copy it into ~/.claude/hooks/ alongside the JS files. Addresses the ESM sub-case flagged in #167 (comment by mrx-arafat). Does not fix the Windows path-with-spaces expansion issues in #167/#78/#72 which have a separate root cause in plugin.json ${CLAUDE_PLUGIN_ROOT} quoting. --- hooks/install.ps1 | 2 +- hooks/install.sh | 2 +- hooks/package.json | 3 +++ hooks/uninstall.ps1 | 2 +- hooks/uninstall.sh | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 hooks/package.json diff --git a/hooks/install.ps1 b/hooks/install.ps1 index 00ec4ea..3ebbe6e 100644 --- a/hooks/install.ps1 +++ b/hooks/install.ps1 @@ -24,7 +24,7 @@ $HooksDir = Join-Path $ClaudeDir "hooks" $Settings = Join-Path $ClaudeDir "settings.json" $RepoUrl = "https://raw.githubusercontent.com/JuliusBrussee/caveman/main/hooks" -$HookFiles = @("caveman-config.js", "caveman-activate.js", "caveman-mode-tracker.js", "caveman-statusline.sh", "caveman-statusline.ps1") +$HookFiles = @("package.json", "caveman-config.js", "caveman-activate.js", "caveman-mode-tracker.js", "caveman-statusline.sh", "caveman-statusline.ps1") # Resolve source — works from repo clone or remote $ScriptDir = if ($PSScriptRoot) { $PSScriptRoot } else { $null } diff --git a/hooks/install.sh b/hooks/install.sh index ba235e0..007b845 100755 --- a/hooks/install.sh +++ b/hooks/install.sh @@ -37,7 +37,7 @@ HOOKS_DIR="$CLAUDE_DIR/hooks" SETTINGS="$CLAUDE_DIR/settings.json" REPO_URL="https://raw.githubusercontent.com/JuliusBrussee/caveman/main/hooks" -HOOK_FILES=("caveman-config.js" "caveman-activate.js" "caveman-mode-tracker.js" "caveman-statusline.sh") +HOOK_FILES=("package.json" "caveman-config.js" "caveman-activate.js" "caveman-mode-tracker.js" "caveman-statusline.sh") # Resolve source — works from repo clone or curl pipe SCRIPT_DIR="" diff --git a/hooks/package.json b/hooks/package.json new file mode 100644 index 0000000..5bbefff --- /dev/null +++ b/hooks/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/hooks/uninstall.ps1 b/hooks/uninstall.ps1 index a00ec83..f73a8f8 100644 --- a/hooks/uninstall.ps1 +++ b/hooks/uninstall.ps1 @@ -11,7 +11,7 @@ $HooksDir = Join-Path $ClaudeDir "hooks" $Settings = Join-Path $ClaudeDir "settings.json" $FlagFile = Join-Path $ClaudeDir ".caveman-active" -$HookFiles = @("caveman-config.js", "caveman-activate.js", "caveman-mode-tracker.js", "caveman-statusline.sh", "caveman-statusline.ps1") +$HookFiles = @("package.json", "caveman-config.js", "caveman-activate.js", "caveman-mode-tracker.js", "caveman-statusline.sh", "caveman-statusline.ps1") # Detect if caveman is installed as a plugin $PluginInstalled = $false diff --git a/hooks/uninstall.sh b/hooks/uninstall.sh index e687a43..5a02b78 100755 --- a/hooks/uninstall.sh +++ b/hooks/uninstall.sh @@ -10,7 +10,7 @@ HOOKS_DIR="$CLAUDE_DIR/hooks" SETTINGS="$CLAUDE_DIR/settings.json" FLAG_FILE="$CLAUDE_DIR/.caveman-active" -HOOK_FILES=("caveman-config.js" "caveman-activate.js" "caveman-mode-tracker.js" "caveman-statusline.sh") +HOOK_FILES=("package.json" "caveman-config.js" "caveman-activate.js" "caveman-mode-tracker.js" "caveman-statusline.sh") # Detect if caveman is installed as a plugin (check plugin cache) PLUGIN_INSTALLED=0