[Windows] Bash tool permanently wedged: hook env (CLAUDE_ENV_FILE) grows on every compact, torn export line fails every command with "line 182: e: command not found" (exit 127)
Summary
On Windows (VSCode extension), the Bash tool inlines the accumulated SessionStart-hook env file (CLAUDE_ENV_FILE) into every command's bash -c preamble without any validation, deduplication, or truncation. In long sessions this block grows unboundedly (hooks re-run and re-append on every compact), and one torn/corrupted export line — produced while two hook processes appended concurrently — permanently wedged the session: every subsequent Bash call (even echo ok) failed with:
/usr/bin/bash: line 182: e: command not found
Exit code 127
e / exp are fragments of the word export. The poisoned env script is cached per session, so the session never recovers; resuming the conversation reloads the corrupted content (error moved to line 190 after a full VSCode restart + resume). Only a brand-new conversation is clean.
Environment
- Claude Code VSCode extension
2.1.211(native binary), Windows 10 Pro 10.0.19045, Git Bash (/usr/bin/bashMSYS2) - SessionStart hooks writing env via
CLAUDE_ENV_FILE(matcherstartup|resume|clear|compact): one user hook writing ~33exportlines with separateappendFileSynccalls, plus a plugin hook (openai-codex plugin) writing 2–3 more lines to the same mechanism
Evidence (from Claude VSCode.log, window 20260716T040938)
The env script size logged by the loader grows in a staircase over a ~2h session — one extra copy of the full export block appended per compact/hook event:
11:10:14 Session environment script ready (1508 chars total)
11:36:40 ... (3071 chars total)
12:15:04 ... (4526 chars total)
12:51:58 ... (6197 chars total) ← then alternates 6197/6089 (two competing hook-file states)
13:03:52 ... (9215 chars total) ← first failing spawn, exit 127, "line 182: e: command not found"
At the failing spawn:
13:03:51.152 File C:\Users\Leona\.claude.json written atomically
13:03:52.731 Hook output does not start with {, treating as plain text
13:03:52.747 Hook output does not start with {, treating as plain text ← two hook processes running
13:03:52.770 [Stall] tool_dispatch_start tool=Bash ...
13:03:52.806 Session environment loaded from 2 hook file(s)
13:03:52.806 Session environment script ready (9215 chars total) ← +3KB jump, torn content
13:03:53.034 [Stall] tool_dispatch_end tool=Bash outcome=error
9215 chars ≈ 190 lines — matching the reported failure lines 182/190. A healthy fresh session's preamble is ~35 lines (verified by dumping /proc/$$/cmdline from inside the Bash tool).
Notes:
- The last successful Bash call was 20 seconds earlier (13:03:32). No user config changed in between.
- Subagents spawned from the wedged session inherited the poisoned env and failed identically.
bash -lcinvoked directly viaspawnSyncon the same machine worked perfectly throughout — the corruption is entirely inside the constructed preamble.
Failure-shape reproduction
A 180-line export block containing one region with an unbalanced quote plus a stranded fragment reproduces the exact user-visible shape — single error, nothing else executes, exit 127:
$ bash repro.sh # 175 lines of export F<i>="x", then:
# export BAD="abc
# export MID="swallowed
# e "
# eval 'echo SHOULD_NOT_PRINT'
# "
repro.sh: line 180: e: command not found
$ echo $?
127
Root cause (three compounding defects)
- Unbounded growth: SessionStart hooks re-run on
compact/resumeand append to the session env; nothing truncates or dedupes by key, so the preamble grows ~1.5KB per compact. - No write/read atomicity: multiple hook processes append to the same mechanism with many small
appendFileSynccalls while the loader reads — a torn line is possible (and observed:e/exp= tornexport). - No validation before inlining: the loader inlines the script into
bash -cverbatim and caches it per session. One bad line (unbalanced quote) silently swallows the rest of the preamble including the user command, and the cache makes it permanent for the session; resume reloads it.
Suggested fixes (any one of these would prevent the wedge)
- Validate each env line (
export KEY="value"shape, balanced quoting) before inlining; drop/log malformed lines instead of poisoning every command. - Deduplicate by key when rebuilding the session env script (also fixes unbounded preamble growth on compact).
bash -nthe assembled preamble once after rebuild; on failure, rebuild from last-known-good instead of caching the poisoned script.
Workarounds we applied (user side)
- Hook now skips env writes when
source === "compact"and batches all exports into a single atomic append — no recurrence since. - Recovery when wedged: start a new conversation (env script cache is per-session). VSCode restart + resume does not help.
Related
- #27987 (CLAUDE_ENV_FILE not sourced on Windows), #24775 (env file session-dir mismatch on resume; loader caching), #15840 (CLAUDE_ENV_FILE empty) — adjacent env-file mechanism issues, none cover this corruption/wedge.