[BUG] Bash tool and SessionStart hooks fail with EEXIST on Windows due to non-idempotent mkdir of session-env
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?
On Windows, every Bash tool call and every SessionStart:resume hook invocation fails with:
EEXIST: file already exists, mkdir 'C:\Users\<user>\.claude\session-env\<session-id>'
The hook actually succeeds — its env file (sessionstart-hook-1.sh) is written to the directory on the first attempt. But a subsequent mkdir of the same path errors out because the harness isn't using idempotent mkdir semantics (no recursive: true / no pre-check).
What Should Happen?
mkdir session-env/<session-id>/ should be idempotent. Directory-already-exists is the normal steady state mid-session and should not raise EEXIST.
Bash tool calls should not fail because of an internal harness setup step. The SessionStart hook should not report "Failed to run" when it actually succeeded.
Error Messages/Logs
EEXIST: file already exists, mkdir 'C:\Users\PC\.claude\session-env\30ccd257-2836-4f6a-9578-002d6bf53ee2'
SessionStart:resume hook UI message:
SessionStart:resume hook ⎿ Failed to run: EEXIST: error file already exists,
mkdir 'C:\Users\PC\.claude\session-env\b09856cb-f292-4edc-b209-b1c8133962d3'
Steps to Reproduce
- On Windows, install Claude Code and a plugin that registers a
SessionStarthook (e.g.codex@openai-codex). - Start a Claude Code session. The hook fires; observe the file is created:
````
%USERPROFILE%\.claude\session-env\<session-id>\sessionstart-hook-1.sh
- Invoke any
Bashtool call (e.g. ask Claude to rungit status). The Bash call returns the EEXIST error
immediately.
- Alternatively, trigger a session resume that re-fires the SessionStart hook — the hook UI reports "Failed to run"
with the same EEXIST.
Reproduces 100% on my machine across multiple sessions on multiple days. Workaround: use the PowerShell tool
instead of Bash — it does not trigger the same setup code path.
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
2.1.143
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
IntelliJ IDEA terminal
Additional Information
Stale-directory accumulation (related symptom, same root cause):
Each session creates a UUID-named subdirectory under %USERPROFILE%\.claude\session-env\ and nothing ever cleans up.
After several days of normal use I had <<ORPHAN_COUNT>> orphan directories, all marked with the Windows ReadOnly
attribute, each containing the same 153-byte sessionstart-hook-1.sh. Worth adding a sweep-on-startup or TTL.
Count your own:
(Get-ChildItem "$env:USERPROFILE\.claude\session-env" -Directory).Count
Suggested fix:
Whichever code path creates session-env/<session-id>/ should use idempotent semantics:
- Node.js:
fs.mkdirSync(path, { recursive: true })instead offs.mkdirSync(path). - Or guard with
if (!fs.existsSync(path)) fs.mkdirSync(path).
Sample failing directory contents:
session-env/b09856cb-f292-4edc-b209-b1c8133962d3/
sessionstart-hook-1.sh 153 bytes (READ-ONLY directory attribute set)
└─ contents:
export CODEX_COMPANION_SESSION_ID='b09856cb-f292-4edc-b209-b1c8133962d3'
export CLAUDE_PLUGIN_DATA='C:/Users/PC/.claude/plugins/data/codex-openai-codex'
Hook source: the failing hook output above is from the codex@openai-codex plugin's SessionStart hook, but the
underlying bug is in the Claude Code harness's session-env setup — any plugin that writes via SessionStart hooks will
trigger the same EEXIST pattern, and every Bash tool call hits it regardless of which plugins are installed.
---
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗