[BUG] Plugin hooks.json entries with shell-spawning command silently skipped on Windows; node-spawning entries trigger normally
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
On Windows 11 + Git Bash, certain entries inside a plugin's hooks/hooks.json are silently skipped by Claude Code's hook dispatcher — no error in stderr, no entry in any plugin-side instrumentation. Other entries in the same hooks.json, with the same matcher and the same async/timeout flags, fire correctly. The only differentiator I have evidence for is the shape of the command string: entries that ultimately invoke a .sh wrapper are skipped, while entries that ultimately invoke a .js script run.
What Should Happen?
All entries in hooks/hooks.json should be invoked according to their matcher. If a hook command cannot be spawned, Claude Code should surface an error rather than silently skip the entry.
Steps to Reproduce
- Install the
everything-claude-codeplugin v2.0.0-rc.1 on Windows 11 (commitc7c7d37f). - Use Claude Code normally for ~10 minutes (dozens of tool calls).
- Add an
appendFileSyncdebug call at the top of~/.claude/plugins/cache/everything-claude-code/everything-claude-code/2.0.0-rc.1/scripts/hooks/plugin-hook-bootstrap.jsto log every invocation to~/.claude/homunculus/_debug.log. - Inspect log: every
mode=nodehook entry in the plugin'shooks.jsonproduces a log line per invocation. Twomode=shellentries (pre:observe:continuous-learning,post:observe:continuous-learning) produce zero log lines, indicating Claude Code never started theirnode -e "..."command at all.
The two skipped entries use this command shape:
node -e "<bootstrap js>" shell scripts/hooks/run-with-flags-shell.sh pre:observe skills/continuous-learning-v2/hooks/observe.sh standard,strict
The 17 working entries use this shape:
node -e "<bootstrap js>" node scripts/hooks/run-with-flags.js <hook-id> <script.js> standard,strict
The <bootstrap js> payload is character-for-character identical between the working and the skipped entries.
Eliminated alternatives (with evidence)
- Matcher:
pre:mcp-health-check(samematcher: "*") fires correctly. - Async/timeout:
post:quality-gate(async: true) andpre:bash:dispatcher(async: true) fire correctly. - Plugin-level failure: 17 of 19 hooks in the same plugin fire, ruling out plugin load failure.
- Working directory / project context: identical between fired and skipped entries.
disabledmarkers / env flags: none set;~/.claude/homunculus/disableddoes not exist;ECC_HOOK_PROFILEunset;ECC_SKIP_OBSERVEunset.
Symptom on the user side
~/.claude/homunculus/projects/<hash>/ is created at session start but stays completely empty — no observations.jsonl, no .last-purge, no project.json, no subdirectories. Manually piping a synthetic event into the .sh wrapper from a real Git Bash terminal works correctly end-to-end and produces the expected file tree, so the script itself is functional. The failure is purely on the Claude Code dispatcher side selecting not to start those two hook commands.
Claude Model
Opus 4.7
Is this a regression?
Don't know — first time installing this plugin version on this machine.
Claude Code Version
2.1.x (current)
Operating System
Windows 11 Pro 26200, Git Bash (mingw64)
Additional context
The plugin's docs (SKILL.md line 141-145, continuous-learning-v2) state plainly: "If installed as a plugin (recommended): No extra settings.json hook block is required. Claude Code v2.1+ auto-loads the plugin hooks/hooks.json". The behaviour observed contradicts that promise for the 2 affected entries.
The plugin author has been informed of the symptom on their tracker as well.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗