Windows: hooks leak shell redirection - zero-byte files created in CWD from '>' tokens in tool input (e.g. '-> Signal:' creates file 'Signal')
Summary
On Windows, when hooks are configured (PreToolUse/PostToolUse via cmd /c ...), Claude Code sessions leave stray zero-byte files in the project CWD whose names are tokens taken from tool input / file content that follows a > character — i.e. the hook input is apparently going through a shell layer that interprets > as output redirection.
Example: a Write tool call creating a Python file containing the type annotation -> Signal: results in an empty file named Signal appearing in the repo root.
Environment
- Claude Code CLI on Windows 11 Home (10.0.26200)
- Primary shell: PowerShell; hooks configured with
cmd /c "IF EXIST ... (node ...\hook-handler.cjs pre-edit) ELSE (node ...)"in.claude/settings.json(claude-flow-style setup) - Model: claude-sonnet-5 session
- Hook handler script itself only reads stdin and writes to fixed paths (audited — it never composes shell commands from tool input)
Observed stray files (all 0 bytes, created in project root)
Over two sessions, names clearly derived from edited file contents / prompts:
| Stray file | Matching fragment in tool input |
|---|---|
| Signal | ... ) -> Signal: (return annotation in a written .py file) |
| `Signal ` (with trailing backtick!) | same fragment, different occurrence |MT5
| | -> tuple[MT5Client ... / MT5 ... content |tuple[MT5Client
| | -> tuple[MT5Client, int]: |dict[str
| | x: dict[str, int]) -> ... |date('now'
| | date('now', ?) inside a written SQL string |Zgoda
| , przyjmujemy, allow, TV,+, c, 0` | fragments of Polish-language user prompts |
The trailing-backtick variant (Signal + ` `) is notable: backtick is PowerShell's escape character, suggesting at least one code path routes the data through PowerShell, while the plain variants look like cmd.exe >` redirection targets.
Additional evidence
The captured hook stdout for SessionStart / UserPromptSubmit events shows the hook input JSON echoed as if typed at a cmd.exe prompt:
Microsoft Windows [Version 10.0.26200.8737]
(c) Microsoft Corporation. Wszelkie prawa zastrze�one.
C:\Users\<user>\<project>>{"session_id":"...","transcript_path":"...","hook_event_name":"UserPromptSubmit","prompt":"..."}
C:\Users\<user>\<project>>
i.e. the JSON payload appears to be delivered to (or through) an interactive-looking cmd.exe layer rather than purely via stdin. If any such layer receives text containing >, cmd.exe creates the redirect file — which matches every stray filename observed (token following > up to the next delimiter).
Repro steps (as observed)
- Windows 11, hooks configured in
.claude/settings.jsonwithcmd /c "IF EXIST ... (node <script>) ELSE (node <script>)"forPreToolUse/PostToolUsematchingWrite|Edit. - Ask Claude to write a Python file containing a return annotation such as
def f() -> Signal: .... - Observe a zero-byte file named
Signalcreated in the project root at the same timestamp as the Write call.
Expected
Hook payloads should never pass through a shell in a way that lets > / < / & / | inside tool input act as shell operators. Payload delivery should be stdin-only (or fully escaped).
Impact
- Pollutes repositories with junk files (which then show up as untracked in git; some users may accidentally commit them).
- Same mechanism is in principle capable of clobbering an existing file via redirection (
> existingfiletruncates), so this is not purely cosmetic.