Skip to content

fix(hooks): make Windows hooks work + self-heal stale settings entries#1

Merged
BernardMasika merged 2 commits into
mainfrom
fix/windows-hook-shell-mismatch
Jul 12, 2026
Merged

fix(hooks): make Windows hooks work + self-heal stale settings entries#1
BernardMasika merged 2 commits into
mainfrom
fix/windows-hook-shell-mismatch

Conversation

@BernardMasika

@BernardMasika BernardMasika commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

fix(hooks): make Windows hooks work, and self-heal stale settings entries

What was happening

On Windows, every prompt was blocked with:

UserPromptSubmit operation blocked by hook:
[if (Get-Command node -ErrorAction SilentlyContinue) { node "...fableit-mode-tracker.js" }]:
/usr/bin/bash: -c: line 1: syntax error near unexpected token `{'

Claude Code runs hook commands through bash on every platform (Git Bash on
Windows), but the commandWindows fields were written in PowerShell
(if (...) { }, Get-Command -ErrorAction). Bash can't parse that, and because
those variants had no trailing ; exit 0, the non-zero exit caused the
UserPromptSubmit hook to block the prompt on every submission.

This bit users through two paths:

  1. Marketplace-plugin install reads hooks/hooks.json directly.
  2. npx fableit install derives a hooks block into ~/.claude/settings.json
    via bin/fableit.js, and on Windows it copied the PowerShell commandWindows
    value in verbatim. This is the copy that actually fired for affected users,
    which is why fixing only hooks.json didn't unblock an existing install.

Changes

hooks/hooks.json — remove the PowerShell commandWindows variants. The
plain command field is already correct and cross-platform: forward slashes
work with node on Windows, and ; exit 0 guarantees a hook never blocks a
prompt. No behaviour change on macOS/Linux.

bin/fableit.js

  • hookEntries() no longer emits the PowerShell variant into settings.json.
    It substitutes the plugin-root placeholder and normalises to forward slashes,
    so the written command is valid bash on Windows too.
  • installClaude() now reconciles instead of appends. Re-running install
    drops any previous fableit entry (including a stale or broken PowerShell one
    from an older version) and re-adds the current correct one. Users self-heal on
    the next update instead of hand-editing settings.json. It's idempotent and
    leaves the user's own unrelated hooks untouched.
  • isOurs now compares with separators normalised, so an entry written with
    either slash style is recognised (the old backslash-only marker would miss a
    bash command). CLI dispatch is guarded with require.main and the helpers are
    exported so they can be unit-tested.

Tests + CI (so this can't regress)

Run under the existing npm test (node --test), on ubuntu-latest and
windows-latest
in CI, since the bug was Windows-specific.

  • tests/hooks.test.js: hooks.json is valid JSON with the right shape; every
    shell-bearing field (command and any legacy commandWindows) parses under
    bash -n (the exact check that catches PowerShell syntax); referenced scripts
    exist; the hook scripts always exit 0 on real, ordinary, empty, and garbage
    stdin. A known-bad fixture is asserted to be rejected, so the guard can't
    silently pass everything.
  • tests/installer.test.js: reconcile replaces a stale PowerShell entry with a
    valid bash one, preserves the user's own hooks, is idempotent, and every
    command the installer emits is valid bash.

Verification

$ npm test
# tests 14
# pass 14
# fail 0

Swapping the pre-fix hooks.json back in fails the suite with a message
pointing straight at the offending commandWindows field.

Note for existing (already-broken) installs

With reconcile in place, affected users are fixed automatically the next time
npx fableit runs (e.g. on update). No manual settings.json editing needed.

Claude Code runs hook commands through bash on every platform (Git Bash on
Windows), but the commandWindows fields were PowerShell, so on Windows bash
failed to parse them and the non-zero exit blocked every prompt via the
UserPromptSubmit hook.

- hooks/hooks.json: drop the PowerShell commandWindows variants; the bash
  command field already works everywhere.
- bin/fableit.js: hookEntries() emits only the bash command with forward
  slashes; installClaude() reconciles instead of appends so broken installs
  self-heal on the next run; isOurs matches either slash style.
- tests/hooks.test.js + tests/installer.test.js: bash-syntax validation of
  every hook command, known-bad fixture, reconcile semantics.
- .github/workflows/test.yml: run npm test on ubuntu-latest and windows-latest.
…hell

On Windows CI, npm runs scripts through cmd.exe, which doesn't expand
tests/*.test.js, so node --test got the literal string and failed. Quote
the pattern and let node's own --test glob expansion (v21+) resolve it,
and pin CI to node 22 so both platforms behave the same.
@BernardMasika BernardMasika merged commit 724e529 into main Jul 12, 2026
2 checks passed
@BernardMasika BernardMasika deleted the fix/windows-hook-shell-mismatch branch July 12, 2026 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant