[BUG] EEXIST: mkdir 'session-env/<UUID>' when .claude is a symlink to OneDrive-synced folder (Windows)

Resolved 💬 6 comments Opened Apr 19, 2026 by alberdran Closed Jun 4, 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?

Bug: EEXIST: mkdir 'session-env/<UUID>' when .claude is a symlink to a OneDrive-synced folder

Summary

On Windows, when ~/.claude is a symbolic link pointing to a folder synchronized by OneDrive, every Bash tool invocation fails with:

EEXIST: file already exists, mkdir 'C:\Users\<user>\.claude\session-env\<UUID>'

This renders the entire CLI unusable — no Bash command, no slash command, no skill, no agent can run.

Environment

| Field | Value |
|---|---|
| Claude Code version | 2.1.113 |
| Model | claude-opus-4-7 (1M context) |
| OS | Windows 11 Pro 10.0.22621 |
| Shell | Git Bash (mingw64) |
| Node.js | bundled with Claude Code |
| Cloud sync | OneDrive (Microsoft 365) with Files On-Demand enabled |
| .claude setup | Symlink: C:\Users\<user>\.claudeC:\Users\<user>\OneDrive\.claude |

Multi-machine sync scenario: user operates on two machines (NB_* and *_Desk) and uses OneDrive to keep .claude (agents, skills, hooks, settings, memory) in sync across both.

Root cause

Three factors combine:

  1. OneDrive applies +R (READONLY) attribute to files and folders under its sync root as part of the "Files On-Demand" / pinning mechanism. Confirmed via:

``
attrib "C:\Users\<user>\.claude\session-env\<UUID>" /S /D
→ A R P (Archive, Readonly, Pinned)
``

  1. Symlink places session-env/ inside the synced area, so the +R propagates to the runtime directory Claude Code uses. Confirmed via:

```
fsutil reparsepoint query "C:\Users\<user>\.claude"
→ IO_REPARSE_TAG_SYMLINK (0xa000000c)

python -c "import os; print(os.readlink(r'C:\Users\<user>\.claude'))"
→ \\?\C:\Users\<user>\OneDrive\.claude
```

  1. Claude Code calls fs.mkdirSync('session-env/<UUID>') without { recursive: true } and without catching EEXIST. When the directory already exists (orphan from an interrupted session, or same UUID reused by claude -c) — or exists with +R attribute — the call throws and the CLI surfaces it as a hard error on every Bash tool use.

Steps to reproduce

  1. On Windows with OneDrive installed, create a symlink:

``powershell
mklink /D "C:\Users\<user>\.claude" "C:\Users\<user>\OneDrive\.claude"
``

  1. Start a Claude Code session: claude
  2. Run any command that uses the Bash tool, e.g. ls.
  3. End the session abruptly (close terminal, crash, Ctrl+C) — session-env/<UUID> stays on disk.
  4. Start a new session using claude -c (reuses same sessionId/UUID).
  5. Try any Bash command → immediate EEXIST error, loops forever.

Alternative repro: on a fresh session, wait a few minutes between Bash calls so OneDrive re-pins the session-env/<UUID> folder with +R, then run another Bash command.

Expected behavior

fs.mkdirSync('session-env/<UUID>') should not fail if the directory already exists and/or has read-only attribute. Either:

  • Use fs.mkdirSync(..., { recursive: true }) (idempotent), or
  • Wrap in try/catch and ignore EEXIST, or
  • Check fs.existsSync(...) before calling mkdirSync.

Actual behavior

Every Bash tool invocation fails with:

EEXIST: file already exists, mkdir 'C:\Users\<user>\.claude\session-env\<UUID>'

The error is not recoverable within the session. Users must either:

  • Kill the session and manually delete the session-env/<UUID> folder (after removing +R with attrib -R /S /D), or
  • Use a workaround hook (see below).

Workaround

I implemented two hooks in settings.json:

1. SessionStart hook — purges orphans on session start

cleanup-session-env.sh:

#!/usr/bin/env bash
set -u
SESSION_ENV_DIR="/c/Users/<user>/.claude/session-env"
[ -d "$SESSION_ENV_DIR" ] || exit 0
# Strip +R recursively
cmd //c "attrib -R \"C:\\Users\\<user>\\.claude\\session-env\" /S /D" >/dev/null 2>&1
# Delete all orphans (at SessionStart, any existing UUID dir is stale)
for dir in "$SESSION_ENV_DIR"/*/; do
  [ -d "$dir" ] || continue
  rm -rf "$dir" 2>/dev/null
done

Registered as:

"SessionStart": [{
  "matcher": "*",
  "hooks": [{ "type": "command", "command": "bash ~/.claude/cleanup-session-env.sh" }]
}]

2. PreToolUse hook for Bash — strips +R before every Bash call

fix-session-env-attr.sh:

#!/usr/bin/env bash
set +e
cmd //c "attrib -R \"C:\\Users\\<user>\\.claude\\session-env\" /S /D" >/dev/null 2>&1
exit 0

Registered as:

"PreToolUse": [{
  "matcher": "Bash",
  "hooks": [{ "type": "command", "command": "bash ~/.claude/fix-session-env-attr.sh" }]
}]

Runtime: ~56ms per Bash call.

This workaround fully mitigates the bug, but is a symptom-level fix.

Proposed fix (upstream)

In the Claude Code source, locate the mkdirSync call that creates session-env/<UUID>. Change:

fs.mkdirSync(sessionEnvPath);

to:

fs.mkdirSync(sessionEnvPath, { recursive: true });

or, more defensively:

try {
  fs.mkdirSync(sessionEnvPath, { recursive: true });
} catch (err) {
  if (err.code !== 'EEXIST') throw err;
}

{ recursive: true } is idempotent under POSIX and Node.js semantics — it does not fail if the directory exists. This one-line change eliminates the bug entirely for the OneDrive-symlink scenario, and also for any other case where the runtime directory might persist between sessions (crashes, claude -c, shared dev environments, etc.).

Impact

  • Affects: any Windows user who uses OneDrive to sync .claude across machines via symlink (a recommended pattern for multi-device setups).
  • Severity: critical — blocks all tool use, including the very Bash tool needed to diagnose the issue.
  • Frequency: every session once the orphan is created, until manually cleaned.

Additional context

  • OneDrive cannot be configured to exclude a subfolder of a synced directory — the sync root is an all-or-nothing selection. So "just unsync session-env/" is not a user-available option.
  • Removing the symlink breaks the multi-machine .claude sync workflow the user relies on (agents, skills, memory, instincts all depend on shared state).
  • Similar scenarios may apply to other cloud sync tools that pin files as readonly: Dropbox (selective sync), Google Drive (streaming), iCloud on Windows.

---

Thanks for Claude Code — happy to provide more diagnostics or test a fix build.

What Should Happen?

fs.mkdirSync('session-env/<UUID>') should be idempotent — it should not throw when the directory already exists or when it has
the READONLY attribute applied by OneDrive.

Specifically, the CLI should:

  1. Use fs.mkdirSync(sessionEnvPath, { recursive: true }) instead of the non-recursive form, since { recursive: true } silently

succeeds if the directory exists (standard Node.js behavior).

  1. Optionally strip the READONLY attribute before use on Windows, or just tolerate its presence (Node can write into +R

directories on Windows as long as the process has permission).

  1. As a result, Bash tool invocations should work reliably in all scenarios:
  • Fresh session with a new UUID (current behavior — works).
  • Resumed session via claude -c that reuses the same UUID (currently broken — EEXIST on first Bash call).
  • Session where session-env/<UUID> was orphaned by a previous crash (currently broken).
  • Long session where OneDrive re-pins the directory with +R mid-session between Bash calls (currently broken).

The CLI should never surface a filesystem housekeeping error to the user during tool execution — this directory management is an
implementation detail that should be invisible.

Error Messages/Logs

Steps to Reproduce

Prerequisites

  • Windows 10 or 11
  • OneDrive installed and signed in, with "Files On-Demand" enabled (default setting)
  • Claude Code 2.1.113 (or likely any recent version that creates session-env/<UUID>/)
  • Git Bash (or equivalent) for running bash commands

One-time setup — create the symlink scenario

Run in PowerShell as Administrator (required for symlinks):

# 1. Move the existing .claude folder into OneDrive (if it doesn't already exist there)
Move-Item "$env:USERPROFILE\.claude" "$env:USERPROFILE\OneDrive\.claude"

# 2. Create symlink pointing to the OneDrive location
New-Item -ItemType SymbolicLink -Path "$env:USERPROFILE\.claude" -Target "$env:USERPROFILE\OneDrive\.claude"

# 3. Confirm symlink was created and OneDrive is syncing it
fsutil reparsepoint query "$env:USERPROFILE\.claude"
# Expected output contains: Reparse Tag Value : 0xa000000c (IO_REPARSE_TAG_SYMLINK)

Wait a few minutes for OneDrive to finish the initial sync.

Reproduction A — orphan from interrupted session

  1. Start a fresh Claude Code session:

claude

  1. Ask Claude to run any Bash command:

> run ls in my home directory

  1. The session-env/<UUID>/ directory is created. Confirm:

ls ~/.claude/session-env/
# Shows one folder named with the current session UUID

  1. Kill the session abruptly (close the terminal window, or force-kill the claude process). Do not exit gracefully — the orphan

must remain.

  1. Verify the orphan persists and inspect its attributes:

ls ~/.claude/session-env/
# Still shows the UUID folder

attrib "C:\Users\<USER>\.claude\session-env\<UUID>"
# Output: A R P (Archive + READONLY + Pinned by OneDrive)

  1. Start a new session with the same UUID (or wait for OneDrive to re-pin):

claude -c # resumes previous sessionId

  1. Ask Claude to run ANY Bash command:

> run pwd

  1. Bug reproduces:

EEXIST: file already exists, mkdir 'C:\Users\<USER>\.claude\session-env\<UUID>'

  1. Every subsequent Bash tool call fails with the same error. The CLI is effectively unusable.

Reproduction B — mid-session re-pin

  1. Start a fresh session (new UUID):

claude

  1. Run one Bash command successfully (ls). Confirm session-env/<UUID>/ was created.
  2. Leave Claude Code idle for ~5–10 minutes while OneDrive re-syncs (you'll see OneDrive activity in the system tray).
  3. Verify the attribute was re-applied:

attrib "C:\Users\<USER>\.claude\session-env\<UUID>"
# R flag is back

  1. Run another Bash command in the same session → same EEXIST error.

Manual cleanup (to recover between repros)

Required because the CLI can't clean up its own state once the error starts:

# PowerShell or Git Bash
attrib -R "C:\Users\<USER>\.claude\session-env" /S /D
rm -rf ~/.claude/session-env/*

Evidence — why the error happens

# 1. Confirm .claude is a symlink into OneDrive
python -c "import os; print(os.readlink(r'C:\Users\<USER>\.claude'))"
# Output: \\?\C:\Users\<USER>\OneDrive\.claude

# 2. Confirm OneDrive applies +R to subdirectories
attrib "C:\Users\<USER>\.claude\session-env" /S /D
# Every subdirectory shows "A R P"

# 3. Minimal Node.js repro (simulates what Claude Code does)
# Save as test-mkdir.js and run with node test-mkdir.js:
const fs = require('fs');
const path = require('path');
const os = require('os');

const dir = path.join(os.homedir(), '.claude', 'session-env', 'test-uuid-' + Date.now());

// First call: creates the directory
fs.mkdirSync(dir);
console.log('First mkdirSync: OK');

// Second call with the SAME path: reproduces the exact error Claude Code surfaces
try {
fs.mkdirSync(dir);
console.log('Second mkdirSync: OK (unexpected)');
} catch (err) {
console.log('Second mkdirSync failed:', err.code, err.message);
// Outputs: EEXIST file already exists, mkdir '...'
}

// Workaround that would fix it — idempotent:
fs.mkdirSync(dir, { recursive: true });
console.log('mkdirSync with recursive: OK (this is the fix)');

Replace <USER> with the actual Windows username in all commands above.

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.113

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

PowerShell

Additional Information

_No response_

View original on GitHub ↗

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