claude-code-plugin-hacker

Installation
SKILL.md

Claude Code Plugin Hacker

Specialist skill for debugging and fixing Claude Code plugin system issues.

When to Use

  • SessionStart:startup hook error or any hook error on launch
  • Plugin behavior after enable/disable doesn't match expectations
  • Investigating how plugins, hooks, and skills interact
  • Fixing broken plugin hooks across projects
  • Auditing which plugins inject what into sessions

Critical Knowledge

enabledPlugins: false Is NOT a Kill Switch

Component When false When true
hooks Still execute Execute
skills Still accessible via Skill tool Accessible
CLAUDE.md Likely not loaded Loaded
agents Likely not registered Registered

To truly stop a hook: edit hooks.json in cache, delete cache dir, or fix the script.

Plugin Cache Is Versioned

~/.claude/plugins/cache/<marketplace>/<plugin>/<hash>/
  • <hash> = git commit hash of ~/.claude repo (when it's a git repo)
  • New commit → new cache directory → new copy of hooks/skills
  • Fixing ONE cached version does NOT fix others
  • Always use find ... -exec for bulk fixes

Hook Execution Environment

Claude Code runs hooks in a minimal environment:

  • Restricted PATH (may lack timeout, jq, etc.)
  • Minimal environment variables (many unset)
  • Short timeout on hook execution
  • stdin receives JSON with hook context
  • stdout must emit valid JSON for context injection
  • Non-zero exit code → "hook error" displayed to user

Phase 1: Audit

Enumerate ALL hook sources. Run these diagnostics:

# 1. List ALL plugin hooks.json files
find ~/.claude/plugins/cache -name "hooks.json" -path "*/hooks/*" \
  -exec echo "=== {} ===" \; -exec cat {} \;

# 2. Find ALL SessionStart hooks specifically
find ~/.claude/plugins/cache -name "hooks.json" -path "*/hooks/*" \
  -exec grep -l "SessionStart" {} \;

# 3. User-defined hooks in settings.json
grep -A5 '"SessionStart"' ~/.claude/settings.json

# 4. List enabled plugins
grep -A1 'enabledPlugins' ~/.claude/settings.json | head -30

# 5. Count cached versions per plugin
for d in ~/.claude/plugins/cache/*/*/; do
  plugin=$(echo "$d" | rev | cut -d/ -f3-2 | rev)
  echo "$plugin: $(ls -d ${d}*/ 2>/dev/null | wc -l) versions"
done

Output: Complete map of hooks × plugins × versions.

Phase 2: Diagnose

Test each suspicious hook in isolation:

# Test a hook script directly
echo '{"source":"startup","session_id":"diag-test"}' | \
  CLAUDE_PLUGIN_ROOT="<plugin_root>" \
  <hook_command> 2>&1
echo "EXIT: $?"

Known Failure Patterns

Pattern Signature Root Cause Fix
Permission denied EACCES, exit 126/127 .sh script missing +x chmod +x all copies
Unset variable exit 1 with no output set -u in bash script Remove -u from set
Node module mismatch NODE_MODULE_VERSION N vs M Native addon for wrong Node.js npm rebuild <module>
Database locked SqliteError: database is locked Concurrent DB access Retry logic or skip
Command not found timeout: not found Minimal PATH Use builtins or full paths
JSON parse error SyntaxError: Unexpected token Hook outputs non-JSON to stdout Fix script output

Permission Check (bulk)

# Find ALL hook scripts without execute permission
find ~/.claude/plugins/cache -name "*.sh" ! -perm -u+x \
  -exec echo "BROKEN: {}" \;

Bash Safety Check

# Find dangerous set -u in hook scripts
grep -rn "set.*-u" ~/.claude/plugins/cache/*/hooks/ \
  ~/.claude/plugins/cache/*/*/hooks/ 2>/dev/null

Node.js Native Addon Check

# Find better-sqlite3 or other native addons
find ~/.claude/plugins/cache -name "*.node" -exec sh -c \
  'echo "=== $1 ==="; file "$1"' _ {} \;

Phase 3: Fix

Fix 1: Bulk chmod (permission denied)

find ~/.claude/plugins/cache/<plugin> -name "<script>.sh" \
  ! -perm -u+x -exec chmod +x {} \; -exec echo "FIXED: {}" \;

Fix 2: Remove set -u (unset variable errors)

find ~/.claude/plugins/cache/<plugin> -name "<script>" \
  -exec sed -i '' 's/set -euo pipefail/set -eo pipefail 2>\/dev\/null || true/' {} \; \
  -exec echo "FIXED: {}" \;

Fix 3: Rebuild native addons

cd ~/.claude/plugins/cache/<plugin>/<version>
npm rebuild <module-name>

Fix 4: Remove problematic hook entirely

Edit hooks.json to empty the event:

{ "hooks": { "SessionStart": [] } }

Apply to ALL cached versions with find ... -exec.

Fix 5: Nuclear option — delete plugin cache

rm -rf ~/.claude/plugins/cache/<marketplace>/<plugin>/

Plugin will be re-cached on next session start.

Phase 4: Verify

# Re-test all hooks after fixes
find ~/.claude/plugins/cache -name "hooks.json" -path "*/hooks/*" \
  -exec grep -l "SessionStart" {} \; | while read f; do
    dir=$(dirname "$f")
    root=$(dirname "$dir")
    echo "=== Testing: $root ==="
    # Extract command from hooks.json and test it
    cmd=$(python3 -c "
import json, sys
h=json.load(open('$f'))
for entry in h.get('hooks',{}).get('SessionStart',[]):
  for hook in entry.get('hooks',[]):
    print(hook['command'].replace('\${CLAUDE_PLUGIN_ROOT}','$root'))
" 2>/dev/null)
    if [ -n "$cmd" ]; then
      echo '{"source":"startup"}' | CLAUDE_PLUGIN_ROOT="$root" eval "$cmd" >/dev/null 2>&1
      echo "EXIT: $?"
    fi
  done

Decision Tree

Hook error on startup?
├─ Run Phase 1 Audit
│  └─ Which plugins have SessionStart hooks?
│     ├─ Test each hook (Phase 2)
│     │  ├─ Permission denied → Fix 1 (chmod, ALL copies)
│     │  ├─ Exit 1, no output → Fix 2 (remove set -u)
│     │  ├─ NODE_MODULE_VERSION → Fix 3 (npm rebuild)
│     │  ├─ All pass locally but still errors →
│     │  │  Check if there are MORE cached versions
│     │  │  you didn't fix (Phase 1, count versions)
│     │  └─ Unknown error → Fix 4 (remove hook) or Fix 5 (nuke cache)
│     └─ Verify all hooks pass (Phase 4)
Plugin behavior unexpected after disable?
├─ Remember: `false` doesn't stop hooks/skills
├─ To stop hooks: edit hooks.json or delete cache
└─ To stop skills: delete cache (no other way)

Anti-Patterns

Don't Why Do Instead
Set enabledPlugins: false to stop hooks Doesn't work — hooks still run Edit hooks.json or delete cache
Fix one cached version Other versions still broken Use find -exec for ALL versions
Add set -euo pipefail to hook scripts -u kills script in minimal runtime Use set -e or set -eo pipefail
Test hooks only from your shell Your shell has richer env than CC runtime Test with minimal PATH and env
Assume manual test = CC runtime CC may pipe stdin differently, set timeout Always verify with actual CC launch
Related skills
Installs
2
GitHub Stars
1
First Seen
6 days ago