[BUG] Native installer/updater fails with EEXIST mkdir when XDG dirs live in a OneDrive sync root (orphaned Files-On-Demand placeholders)

Open 💬 0 comments Opened Jun 11, 2026 by tcaesvk

What's Wrong?

The native Windows installer (irm https://claude.ai/install.ps1 | iex) and claude update fail with EEXIST on mkdir when the XDG directories (~/.local/share, ~/.local/state) live inside a OneDrive Files-On-Demand sync root — in my case via a directory symlink %USERPROFILE%\.localC:\Users\<user>\OneDrive\.local, used intentionally to sync dotfiles across desktops.

The failing directories are orphaned cloud placeholders: they show up in the parent directory listing with reparse tag 0x9000E01A (cloud files), but cannot be stat'ed — Get-Item/Remove-Item/attrib all report "path not found" for the same entry that dir lists. Node's fs.mkdirSync(p, {recursive: true}) then hits the contradiction: mkdir returns EEXIST, the follow-up stat fails, so it throws EEXIST and the installer aborts.

Key facts verified on the affected machine:

  • A healthy OneDrive placeholder is not the problem. A freshly created directory inside the sync root is converted to a placeholder (tag 0x9000E01A) within ~5 seconds, and fs.mkdirSync({recursive: true}) succeeds on it — both through the symlink path and the direct path.
  • Only the orphaned state breaks installation (listed in parent enumeration, ENOENT on stat). These orphans appeared after the items had been deleted on the cloud side (including emptying the OneDrive recycle bin); the deletion never fully applied to the local placeholders.
  • While the sync state was wedged, local remediation was reverted within seconds: rd /s /q + recreate as a real directory + attrib +P ("Always keep on this device") — the sync engine put the broken placeholder back, and the next install attempt failed identically.
  • The hint printed by the installer, Try running with --force to override checks, does not help: the install script does not forward a --force flag, and running the downloaded binary with claude.exe install --force manually fails with the same EEXIST.

What Should Happen?

  1. Diagnose the contradictory state. When mkdir returns EEXIST but the same path cannot be stat'ed, the installer/updater should print an actionable message (cloud-placeholder / wedged OneDrive sync state, with remediation such as resetting the OneDrive sync database) instead of a bare EEXIST plus a misleading --force hint.
  2. Ideally, attempt remediation: remove the orphaned directory entry and retry the mkdir, or create the tree under a temporary name and rename into place.
  3. Consider machine-local placement for per-machine runtime state. ~/.local/state/claude/locks is inherently machine-local; placing it under a user-synced XDG tree invites both this failure mode and lock files syncing between machines. Mapping ~/.local into OneDrive for cross-desktop dotfile sync is a deliberate, long-term setup — "move your XDG dirs out of OneDrive" is a workaround, not an acceptable requirement.

Error Messages/Logs

PS> irm https://claude.ai/install.ps1 | iex
Setting up Claude Code...

Checking installation status...
Installing Claude Code native build latest...
× Installation failed

EEXIST: file already exists, mkdir '%USERPROFILE%\.local\share\claude\versions'

Try running with --force to override checks
Write-Error: Installation failed (exit code 1)
PS> claude update
Current version: 2.1.173
Checking for updates to latest version...
Error: Failed to install native update
Error: EEXIST: file already exists, mkdir '%USERPROFILE%\.local\state\claude\locks'
Try running "claude doctor" for diagnostics

Diagnostic signature of the orphaned placeholder (same entry, contradictory answers):

PS> cmd /c dir /A "%USERPROFILE%\.local\share\claude\versions"
2026-06-12  08:08                 0 2.1.173        <- listed by enumeration

PS> Get-Item "%USERPROFILE%\.local\share\claude\versions\2.1.173"
Get-Item: Cannot find path '...' because it does not exist.   <- stat fails

PS> fsutil reparsepoint query "%USERPROFILE%\.local\share\claude\versions"
Reparse Tag Value : 0x9000e01a                                 <- cloud placeholder

Control experiment showing healthy placeholders are fine:

PS> node -e "require('fs').mkdirSync('%USERPROFILE%/.local/share/test-dir', {recursive:true})"
(succeeds — even though fsutil shows the same 0x9000e01a tag on the directory)

Steps to Reproduce

A deterministic repro is hard because it requires the OneDrive sync state to be wedged; the diagnostic signature above is the reliable identifier. The sequence that produced it here:

  1. Windows 11, OneDrive with Files-On-Demand enabled.
  2. mklink /D %USERPROFILE%\.local %USERPROFILE%\OneDrive\.local (cross-desktop dotfile sync).
  3. Install Claude Code natively; ~/.local/share/claude/... and ~/.local/state/claude/... are created inside the sync root and become cloud placeholders.
  4. The claude directories get deleted on the cloud side (e.g. from another machine or the web UI, recycle bin emptied) while the local client holds placeholders.
  5. Local result: orphaned placeholder entries (listed in parent, ENOENT on stat).
  6. Run the install script or claude updateEEXIST: file already exists, mkdir ....

Workaround that fixed it durably: point the XDG variables outside the sync root (XDG_DATA_HOME/XDG_STATE_HOME%LOCALAPPDATA%\xdg\...) — confirmed the installer and updater honor these. But this defeats the purpose of syncing ~/.local across desktops, hence this report.

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Claude Code Version

2.1.173 (Claude Code), native win32-x64

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

PowerShell

Additional Information

  • Existing open EEXIST issues (#67408, #60006, #59622, #66205) all concern non-idempotent mkdir of ~/.claude subdirectories at runtime — different paths and a different root cause (those reproduce with healthy directories; this one requires the orphaned-placeholder state in the native install tree under ~/.local).
  • ~/.local/bin/claude.exe (the self-contained launcher) also lives inside the sync root in this setup; it currently works with the pinned (+P) attribute, but the same orphaned-placeholder failure mode presumably applies to launcher replacement during updates.

View original on GitHub ↗