[Windows] Hooks receive stdin as TTY instead of pipe — tool input never received
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
## Environment
- OS: Windows 11 Enterprise
- Claude Code version: (latest)
- Shell: Git for Windows (git-bash.exe / bash.exe)
## Description
On Windows, hook commands always receive process.stdin.isTTY === true, meaning stdin is
connected to a pseudo-terminal (PTY) instead of a pipe. As a result, hooks never receive
any tool input data — process.stdin emits no data events and the end event never
fires. The 500ms fallback timeout resolves with 0 bytes.
This makes it impossible to implement PreToolUse/PostToolUse hooks that inspect tool input
(e.g. blocking reads of sensitive files, checking file paths before writes, etc.).
What Should Happen?
Hook processes should receive tool input as JSON on stdin (a proper pipe), matching the
behaviour on Linux/macOS and the documented hook contract:
{
"tool_name": "Read",
"tool_input": { "file_path": "/path/to/file" },
...
}
Error Messages/Logs
Steps to Reproduce
## Reproduction
Hook command (settings.json):
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read",
"hooks": [{ "type": "command", "command": "node ./hooks/read_hook.js" }]
}
]
}
}
Hook script (read_hook.js, ES module):
import { writeFileSync } from 'fs';
async function main() {
writeFileSync('debug.txt', isTTY=${process.stdin.isTTY}\n);
const raw = await new Promise((resolve) => {
let data = '';
process.stdin.on('data', (chunk) => (data += chunk));
process.stdin.on('end', () => resolve(data));
setTimeout(() => resolve(data), 500); // fallback
});
writeFileSync('debug.txt', bytes received: ${raw.length}\ndata: ${raw}\n);
process.exit(0);
}
main();
Result in debug.txt after hook fires:
isTTY=true
bytes received: 0
data:
Investigation
- process.stdin.isTTY === true confirms stdin is a PTY, not a pipe
- CLAUDE_CODE_GIT_BASH_PATH env var is visible inside the hook — Claude Code does
inject it correctly
- Changing CLAUDE_CODE_GIT_BASH_PATH in ~/.claude/settings.json from
git-bash.exe to bin\bash.exe (non-MinTTY) is respected, but isTTY remains
true — the PTY behaviour is not resolved by this change
- readFileSync(0, 'utf8') (synchronous stdin read) causes a visible terminal/console
window to open, waiting for user keyboard input — confirming stdin is attached to
a live console device, not a pipe
Claude Model
Sonnet (default)
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.77 (Claude Code)
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
PowerShell
Additional Information
_No response_
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗