[BUG] EEXIST on session-env mkdir despite recursive:true (v2.1.108, Windows)

Resolved 💬 3 comments Opened Apr 26, 2026 by renatomiyaoka Closed Apr 26, 2026

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?

The Bash tool fails with EEXIST: file already exists, mkdir '<config-dir>\session-env\<session-id>' at random points in long-running sessions and reliably on session resume. The harness's mkdir call (in function CU1 of cli.js v2.1.108) uses {recursive: true} but is not wrapped in the try/catch-EEXIST pattern that other mkdir call sites in the same file use. Whenever the harness tries to create a session-env/<session-id> folder that already exists on disk (session-id reused across resumes, folder not cleaned from a previous run, or a sync filter/race left it behind), the Bash tool fails before the user's command runs.

In our case the folder accumulated 222 orphan empty session-env/<uuid>/ directories in 2 days of normal use (vs 209 in 14 days before we relocated config to a OneDrive-synced path on the same disk), so the leak happens regardless of sync — OneDrive only amplifies the timing.

What Should Happen?

Bash tool should not fail with EEXIST. Either mkdir with recursive: true should be idempotent (the documented Node.js behavior), or the call in CU1 should be wrapped in the same defensive try/catch-EEXIST pattern that cli.js already uses at other mkdir call sites.

Error Messages/Logs

EEXIST: file already exists, mkdir 'D:\.claude\session-env\12f6195a-0e63-4ad2-ae5c-65b5ffdbc94b'

Steps to Reproduce

  1. Run a Claude Code session that executes Bash tool calls (this creates ~/.claude/session-env/<session-id>/).
  2. End the session without letting cleanup run (close terminal, OS sleep, kill -9, or simply let folders accumulate across many sessions).
  3. Resume a session whose session-id matches an existing folder, OR open a new session in a config dir where many old session-env/* folders remain.
  4. Next Bash tool invocation → "EEXIST: file already exists, mkdir ..." before the command runs.

Reproduces reliably on Windows. We hit it daily for ~2 days before tracing it. Inspecting the minified cli.js confirmed the missing try/catch in CU1.

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.108 (Claude Code)

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Windows Terminal

Additional Information

**Root cause from cli.js (v2.1.108) inspection:**

```js
// Function CU1 — creates the session-env directory
async function CU1(){
  let q = xR8(A7(), "session-env", L8());  // path.join(configDir, "session-env", sessionId)
  return await aqz(q, {recursive:!0}), q
}

Compare to the defensive pattern used elsewhere in cli.js for mkdir:

){try{await jD5(q,{recursive:!0,...K})}catch(_){if(g1(_)!=="EEXIST")throw _}}

CU1 is missing the try/catch. Per Node.js docs, fs.mkdir(path, {recursive: true}) should not throw EEXIST when the path exists, but on Windows (likely interaction with reparse points, sync filters, or atime races) it does. Fix appears to be a one-liner: wrap the aqz call in CU1 (and any analogous helpers) in the same try/catch pattern.

Workaround we deployed (reliable):

SessionStart + PreToolUse(Bash) hook in ~/.claude/settings.json that deletes empty session-env/* entries older than 5 minutes before the harness gets a chance to call mkdir on a colliding path:

$cutoff = (Get-Date).AddMinutes(-5)
Get-ChildItem "$env:CLAUDE_CONFIG_DIR\session-env" -Directory -Force | ForEach-Object {
    if ($_.LastWriteTime -lt $cutoff) {
        $hasContent = Get-ChildItem $_.FullName -Force -Recurse -EA SilentlyContinue | Select-Object -First 1
        if (-not $hasContent) { Remove-Item $_.FullName -Recurse -Force -EA SilentlyContinue }
    }
}

This works but is invisible-friction Windows users shouldn't need. Happy to provide additional logs, repro project, or PR if helpful.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗