claude-code - 💡(How to fix) Fix [BUG] EEXIST: mkdir 'session-env/<UUID>' when .claude is a symlink to OneDrive-synced folder (Windows) [2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#50886Fetched 2026-04-20 12:10:20
View on GitHub
Comments
2
Participants
2
Timeline
6
Reactions
0
Author
Timeline (top)
labeled ×4commented ×2

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.

Error Message

  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.
  2. Try any Bash command → immediate EEXIST error, loops forever. The error is not recoverable within the session. Users must either: The CLI should never surface a filesystem housekeeping error to the user during tool execution — this directory management is an

Error Messages/Logs

  1. Every subsequent Bash tool call fails with the same error. The CLI is effectively unusable.
  2. Run another Bash command in the same session → same EEXIST error. Required because the CLI can't clean up its own state once the error starts: Evidence — why the error happens // Second call with the SAME path: reproduces the exact error Claude Code surfaces

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)
  2. 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
  3. 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.

Fix Action

Workaround

I implemented two hooks in settings.json:

Code Example

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

---

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

---

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

---

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

---

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

---

#!/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

---

"SessionStart": [{
  "matcher": "*",
  "hooks": [{ "type": "command", "command": "bash ~/.claude/cleanup-session-env.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

---

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

---

fs.mkdirSync(sessionEnvPath);

---

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

---

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

---
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • 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

FieldValue
Claude Code version2.1.113
Modelclaude-opus-4-7 (1M context)
OSWindows 11 Pro 10.0.22621
ShellGit Bash (mingw64)
Node.jsbundled with Claude Code
Cloud syncOneDrive (Microsoft 365) with Files On-Demand enabled
.claude setupSymlink: 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)
  2. 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
  3. 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:
    mklink /D "C:\Users\<user>\.claude" "C:\Users\<user>\OneDrive\.claude"
  2. Start a Claude Code session: claude
  3. Run any command that uses the Bash tool, e.g. ls.
  4. End the session abruptly (close terminal, crash, Ctrl+C) — session-env/<UUID> stays on disk.
  5. Start a new session using claude -c (reuses same sessionId/UUID).
  6. 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).
  2. 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).
  3. 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
  2. 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.
  2. 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
  2. Ask Claude to run ANY Bash command:

run pwd

  1. Bug reproduces: EEXIST: file already exists, mkdir 'C:\Users<USER>.claude\session-env<UUID>'
  2. 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
  2. Run one Bash command successfully (ls). Confirm session-env/<UUID>/ was created.
  3. Leave Claude Code idle for ~5–10 minutes while OneDrive re-syncs (you'll see OneDrive activity in the system tray).
  4. 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

extent analysis

TL;DR

The most likely fix is to modify the fs.mkdirSync call in Claude Code to use the recursive option, allowing it to silently succeed if the directory already exists.

Guidance

  • Identify the fs.mkdirSync call in the Claude Code source that creates the session-env/<UUID> directory and modify it to use the recursive option: fs.mkdirSync(sessionEnvPath, { recursive: true }).
  • Alternatively, wrap the fs.mkdirSync call in a try-catch block to ignore the EEXIST error if the directory already exists.
  • Verify that the modification fixes the issue by running the reproduction steps provided in the issue report.
  • Consider implementing the proposed workaround hooks (SessionStart and PreToolUse) as a temporary solution until the upstream fix is available.

Example

// Before
fs.mkdirSync(sessionEnvPath);

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

Notes

  • The issue is specific to Windows users who use OneDrive to sync the .claude directory via a symlink.
  • The proposed fix should be applied to the Claude Code source code.
  • The workaround hooks provided in the issue report can be used as a temporary solution until the upstream fix is available.

Recommendation

Apply the workaround by implementing the proposed SessionStart and PreToolUse hooks, as the upstream fix may take time to be released.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

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.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING