Windows: exec-form hook args dropped, still routed through bash.exe, causing eval_stdin crash

Status Open
Reported on v2.1.117
Maintainer reply None cached
Activity 0 comments · opened Aug 29, 2026

Environment

  • Claude Code version: 2.1.117
  • OS: Windows 11
  • Shell available: Git for Windows (Git Bash present at C:\Program Files\Git\bin\bash.exe), PowerShell 7

Summary

Converting a hook entry in settings.json from shell form to exec form (adding an args array, per the hooks reference) does not spawn the target executable directly on this Windows setup. The hook process is still launched through bash.exe -c, and the args array is dropped entirely, so the target process receives zero arguments.

For a Node-based hook, this means node starts with no script path. Since stdin is not a TTY (Claude Code pipes the hook JSON payload on stdin), Node falls into eval_stdin and tries to execute the piped JSON payload as JavaScript source, crashing with a syntax error on every single hook firing.

Steps to reproduce

  1. Take any working shell-form hook entry, for example:

``json
{
"type": "command",
"command": "node C:/path/to/hook.mjs",
"timeout": 5
}
``

  1. Convert it to exec form per the hooks reference:

``json
{
"type": "command",
"command": "node",
"args": ["C:/path/to/hook.mjs"],
"timeout": 5
}
``

  1. Trigger the hook (start a new session, or fire the matching tool event).

Expected behavior

Per the hooks reference, exec form should spawn node directly with C:/path/to/hook.mjs as argv[1], with no shell involved.

Actual behavior

  • Live process monitoring (Win32_ProcessStartTrace and polled Win32_Process snapshots) shows the hook is still launched as:

``
"C:\Program Files\Git\bin\bash.exe" -c node
`
with a
conhost.exe spawned as its child, and the node.exe` child that follows shows no arguments in its command line.

  • The hook fails with:

``
SyntaxError: Unexpected token ':'
at makeContextifyScript (node:internal/vm:185:14)
at compileScript (node:internal/process/execution:383:10)
at evalTypeScript (node:internal/process/execution:256:22)
at node:internal/main/eval_stdin:53:5
`
This is Node trying to
eval the piped hook JSON payload ({"session_id":...}`) as JavaScript, because it received no script argument.

  • Confirmed in isolation: added a single additive, non-blocking diagnostic hook using exec form that only logs process.argv and stdin to a file. The log file was never created, meaning the diagnostic script never started executing at all before the crash.
  • This reproduced on 48 of 48 converted hook entries (every hook in a large custom configuration), and broke hooks on both newly started sessions and already-running sessions.

Impact

Any hook entry using exec form (args array) on Windows with Git Bash installed appears to be non-functional. Anyone following the hooks reference's own recommendation ("To avoid shell quoting entirely, add args: [] to switch to exec form") on this platform combination will break every hook that receives stdin JSON, since Node has no script to run and instead tries to execute the payload as code.

Workaround

Reverting to shell form ("command": "node C:/path/to/hook.mjs", no args field) restores normal operation. This still routes every hook through bash.exe, which spawns a visible conhost.exe console window on every single hook firing, on every tool call. Exec form was the documented way to avoid that shell hop, but it does not work as documented on this platform.

View original on GitHub ↗