Skip to content

fix: make the hook cross-platform by removing the bash wrapper#33

Merged
fahadsiddiqui merged 1 commit into
datumbrain:mainfrom
jesse-quinn:fix/windows-node-only-hook
Jul 21, 2026
Merged

fix: make the hook cross-platform by removing the bash wrapper#33
fahadsiddiqui merged 1 commit into
datumbrain:mainfrom
jesse-quinn:fix/windows-node-only-hook

Conversation

@jesse-quinn

@jesse-quinn jesse-quinn commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Fixes #18

Problem

hooks/hooks.json invoked scripts/prompt-guard-wrapper.sh, a bash-only
script that logged to /tmp with Unix paths. On native Windows (no
WSL/git-bash) the hook fails, which depending on how the exit code is
interpreted either breaks prompting or silently disables protection.

Fix

Eliminate the bash layer entirely and invoke Node directly.

  • hooks/hooks.json now runs node ${CLAUDE_PLUGIN_ROOT}/scripts/prompt-guard.js
    (same ${CLAUDE_PLUGIN_ROOT} interpolation the plugin already used; 10s
    timeout unchanged).
  • scripts/prompt-guard.js absorbs the wrapper's only real job -
    opt-in debug logging. When PRIVACY_GUARD_DEBUG=1, it writes execution
    metadata (timestamp, CLAUDE_PLUGIN_ROOT, cwd, Node version, exit
    path/code) to debug.log under the platform cache dir, resolved
    cross-platform by a small os.platform() helper:
    • POSIX: XDG_CACHE_HOME or ~/.cache (unchanged from before)
    • Windows: %LOCALAPPDATA%
      Matched secret/PII values are never written to the log.
  • Deleted scripts/prompt-guard-wrapper.sh.
  • README debug section updated with the Windows log path.

Behavior parity

  • Block-decision JSON still goes to stdout; process exits 0 on both
    allow and block (a non-zero exit would make the hook protocol treat the
    block as a non-blocking error and let the prompt through).
  • The wrapper discarded stderr when debug was off. Without the wrapper,
    stderr would now reach Claude Code, so internal errors are routed to the
    debug log (when enabled) and stderr stays quiet otherwise. Error paths
    still exit non-zero, matching the wrapper's pass-through of a failing exit
    code (a non-blocking error - the prompt is not silently altered).

dist/ note

These changes touch only scripts/ and hooks/ (and README.md) - no
src/ changes - so dist/ does not change. git status dist/ is clean,
satisfying the CONTRIBUTING requirement that dist/ match src/.

Manual verification (macOS, Node v26.5.0)

Secret prompt - prints block JSON, exits 0:

$ echo "my api key is sk-proj-abc123xyz1234567890" | node scripts/prompt-guard.js
{
  "decision": "block",
  "reason": "...Found 1 sensitive item(s):\n  - OpenAI API Key: sk-p…7890 ..."
}
EXIT=0

Benign prompt - no output, exits 0:

$ echo "what is the weather like today" | node scripts/prompt-guard.js
(no output)
EXIT=0

PRIVACY_GUARD_DEBUG=1 - creates the log with metadata only (no secret):

=== Hook Execution 2026-07-19T02:34:00.057Z ===
CLAUDE_PLUGIN_ROOT: /fake/plugin/root
CWD: /.../claude-code-privacy-guard
Node version: v26.5.0
Exit path: block
Exit code: 0

Grep of the log for the secret value (sk-proj, abc123, 7890) - no match.

npm install && npm run build && npm test: all 159 tests pass; dist/
unchanged.

@jesse-quinn
jesse-quinn force-pushed the fix/windows-node-only-hook branch from 6a26135 to d6a1035 Compare July 19, 2026 03:16
The UserPromptSubmit hook invoked scripts/prompt-guard-wrapper.sh, a
bash-only script that logged to /tmp with Unix paths, so the plugin
broke on native Windows (no WSL/git-bash).

Move the wrapper's sole responsibility - opt-in debug logging - into
scripts/prompt-guard.js and invoke node directly from hooks.json:

- hooks.json now runs "node ${CLAUDE_PLUGIN_ROOT}/scripts/prompt-guard.js"
  (10s timeout unchanged).
- prompt-guard.js writes execution metadata (timestamp, plugin root,
  cwd, node version, exit path/code) to a debug log under the platform
  cache dir - XDG_CACHE_HOME or ~/.cache on POSIX, %LOCALAPPDATA% on
  Windows via a small os.platform() helper - only when
  PRIVACY_GUARD_DEBUG=1. Matched secret/PII values are never logged.
- Behavior parity preserved: block-decision JSON to stdout, exit 0 on
  both allow and block. Internal errors are routed to the debug log
  (when enabled) and stderr stays quiet, matching the wrapper which
  discarded stderr when debug was off.
- Delete scripts/prompt-guard-wrapper.sh; update the README debug docs
  with the Windows path.

Fixes datumbrain#18
@fahadsiddiqui
fahadsiddiqui force-pushed the fix/windows-node-only-hook branch from d6a1035 to f13da47 Compare July 21, 2026 19:54
@fahadsiddiqui
fahadsiddiqui merged commit 83716b1 into datumbrain:main Jul 21, 2026
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.

Windows support: hook wrapper is bash-only and uses /tmp

2 participants