Windows: app state silently lost when %APPDATA%\Claude is a junction — data directory not configurable

Status Open
Maintainer reply None cached
Activity 4 comments · opened Sep 2, 2026

Summary

On Windows, Claude Desktop stores all of its state under %APPDATA%\Claude and this
location is not configurable from inside the app. When that path is a junction or a
symlink, every atomic write the app performs fails permanently, and the app silently
loses all persisted state. Windows blocks reparse points inside the whole AppData tree,
so relocating the data directory by linking is impossible.

Environment

  • Windows 11 Home, 10.0.26200
  • Claude Desktop 1.40609.1.0 (MSIX / Microsoft Store build,

WindowsApps\Claude_1.40609.1.0_x64__pzs8sxrjxfjjc)

  • %APPDATA%\Claude is a directory junction to C:\Gehirn\.claude\programmdaten,

created 2026-08-28 09:19:14

Symptoms

  • First failure in main.log at 2026-08-28 09:20:28 — 74 seconds after the junction

was created. Errors have continued ever since (693 in two days; 33 in the hour after
the most recent restart).

  • Failed to write bridge state: EEXIST ... 'bridge-state.json.tmp'
  • Same for ant-device-registry.json, extensions-blocklist.json,

claude-code-sessions\...\manifest.json, and individual session files.

  • User-visible consequences:
  • "Connect new sessions to Remote Control" resets to off after every restart.
  • When the server rotated the environment id, the app could not persist the new one

and kept re-registering with the stale id, producing
409: A Cowork agent is already registered on <host>.

  • A stale remoteSessionId could not be cleared, producing

404 Failed to connect transport 78 times, so existing windows became unreachable.

  • Recovery required hand-editing bridge-state.json outside the app.

Root cause (measured)

The failing operation is exclusive create (O_CREAT|O_EXCL, i.e. Win32 CREATE_NEW),
which the atomic write-then-rename path uses for its .tmp file. Through a reparse
point inside AppData it fails 100% of the time with ERROR_FILE_EXISTS, even for a
filename that has never existed. Ordinary create/write, rename and read all succeed,
and no stale .tmp file is ever left behind — so this is not a race and not a leftover
lock.

Reproduction (Python, but the syscall is what Node's fs.open(path, 'wx') issues):

import os, sys
d = sys.argv[1]
base = os.path.join(d, "probe.json"); tmp = base + ".tmp"
ok = eexist = 0
for i in range(300):
    try:
        fd = os.open(tmp, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
        os.write(fd, b"{}"); os.close(fd); os.replace(tmp, base); ok += 1
    except FileExistsError:
        eexist += 1
print(d, "ok=%d eexist=%d" % (ok, eexist))

Results (300 attempts each unless noted):

| Target directory | ok | EEXIST |
|---|---|---|
| plain directory in AppData\Roaming | 300 | 0 |
| AppData\Roaming itself | 300 | 0 |
| junction outside AppData (target in Temp) | 300 | 0 |
| junction outside AppData (target on another volume path) | 300 | 0 |
| real directory C:\Gehirn\.claude\programmdaten | 300 | 0 |
| junction in AppData\Roaming (any name, any target) | 0 | 300 |
| symlink (mklink /D) in AppData\Roaming | 0 | 300 |
| junction in AppData\Local (60 attempts) | 0 | 60 |
| junction in AppData\LocalLow (60 attempts) | 0 | 60 |
| junction in C:\Users\<user> (60 attempts) | 60 | 0 |
| junction in C:\Users\<user>\Documents (60 attempts) | 60 | 0 |

So the block covers the entire AppData tree, regardless of link type, name or target.
Side effect worth noting: PowerShell's Test-Path returns False for a reparse point
under AppData even when it exists.

Why this matters

The user cannot move the data directory out of AppData:

  • Linking is blocked by Windows (measured above).
  • --user-data-dir works (verified: 0 EEXIST when the app is started with it), but the

setting then lives in a shortcut rather than in the app. The MSIX Start-menu entry
cannot carry the argument at all, and any other launch path silently falls back to the
old location.

  • Relocating AppData itself via the registry would affect every application on the

machine.

There is no supported way for a user to decide where this application stores its data.

Requests

  1. Make the data directory configurable inside the app (a setting, or a config file

read at startup) — not only via a command-line argument that has to be attached to
every launch path.

  1. Handle EEXIST on the temp file instead of giving up: unlink and retry, or use a

unique temp name. Right now a single unwritable temp path silently disables all state
persistence, including authentication, with no error surfaced in the UI.

  1. Surface persistent state-write failures to the user. All of the above was invisible

in the interface; it only showed up as features quietly not working.

Note from the account holder

I am the user affected. I do not accept that an application decides where my data is
stored on my own computer. If there is no way to configure this, I will cancel my
subscription.

View original on GitHub ↗

3 Comments

webergastro-source · 1 month ago

Follow-up: root cause confirmed — this is MSIX AppData write virtualization

Since filing I was able to pin the mechanism down against Microsoft's own documentation, so this is no longer a guess about wcifs.

What Microsoft documents

Claude Desktop for Windows ships only as MSIX (per Anthropic's own deployment article — x64/arm64 MSIX, no unpackaged installer). For MSIX, Flexible virtualization → "Default MSIX behavior" → AppData / Run-time states that on Windows > 1809 all newly created files and folders in the user's AppData folder are written to a private per-package location (%LOCALAPPDATA%\Packages\<PFN>\LocalCache\Roaming) and merged into the view at runtime; only modifications to existing AppData files are done on the unvirtualized file.

Why that produces this bug

The app's atomic-save pattern is "create <name>.json.tmp with O_CREAT|O_EXCL (CREATE_NEW), then rename over the target". The create half always targets the private redirection store. When %APPDATA%\Claude is a reparse point (junction or symlink), the redirection cannot mirror that directory, and the create fails immediately with EEXIST — including for names that have never existed on the machine. Writes and renames to already-existing files keep working, which is exactly why the failure looks so selective.

Evidence on this machine (Windows 11 Home 26200, Claude 1.40609.1.0 x64, Claude_pzs8sxrjxfjjc):

  • The private store contains ...\LocalCache\Roaming\Claude-3p, but no ...\LocalCache\Roaming\Claude at all — the redirection for the junctioned folder never materialises.
  • No stale .tmp files exist, in the private store or anywhere on the disk, so this is not a leftover-file or race problem.
  • 693 EEXIST in two days; 33 in the first hour after a clean restart. Affected: bridge-state.json, ant-device-registry.json, extensions-blocklist.json, claude_desktop_config.json, manifest.json, session files.

Correction to my original report: I wrote that Windows blocks reparse points across the entire AppData tree. Those measurements were taken from processes running inside the package container, so they describe the MSIX container view, not Windows in general. The scope of the bug is MSIX redirection, not the OS.

Concrete ask — one manifest change

Declare the unvirtualizedResources restricted capability plus either

<desktop6:FileSystemWriteVirtualization>disabled</desktop6:FileSystemWriteVirtualization>

or, better, the granular Windows 11 form:

<virtualization:FileSystemWriteVirtualization>
  <virtualization:ExcludedDirectories>
    <virtualization:ExcludedDirectory>$(KnownFolder:RoamingAppData)\Claude</virtualization:ExcludedDirectory>
    <virtualization:ExcludedDirectory>$(KnownFolder:RoamingAppData)\Claude-3p</virtualization:ExcludedDirectory>
  </virtualization:ExcludedDirectories>
</virtualization:FileSystemWriteVirtualization>

Then writes go to the real %APPDATA%\Claude, redirection is out of the path, and this failure disappears. As a side effect it also resolves the family of "the app reads a different config file than the one Edit Config opens" reports (#26073, #25579, #58421), because the documented path becomes the real path again.

The other two requests from the original report stand independently of this: retry on EEXIST instead of silently giving up, and surface state-save failures in the UI rather than only in main.log. A user whose settings silently fail to persist across restarts currently has no way to know why.

webergastro-source · 27 days ago

Still reproducible as of 2026-09-17, on a much newer build.

Environment: Claude Desktop 2.110.1.0 (MSIX,
WindowsApps\Claude_2.110.1.0_x64__pzs8sxrjxfjjc). Originally reported against
1.40609.1.0. %APPDATA%\Claude is still a junction to
C:\Gehirn\.claude\programmdaten.

Re-ran the probe from the original report, unchanged, directly in
%APPDATA%\Claude — 50 attempts: ok=0, EEXIST=50. Exclusive create still
fails 100% of the time through the reparse point, and no stale .tmp file is
left behind.

The issue is labelled bug / has repro but has had no maintainer response in two
weeks. Is there anything else you need from me to move this forward? Requests 2
(retry or unique temp name on EEXIST) and 3 (surface persistent state-write
failures) would help every affected user even without 1.

webergastro-source · 27 days ago

Addendum, measured today (2026-09-17) on the same machine and build as the
comment above.

Ordinary writes still work — only creation is broken. Files in the data
directory are being updated right now: config.json and git-worktrees.json
at 10:04, buddy-tokens.json at 10:08, the SQLite WAL at 10:09. So this is not
"the directory is unwritable"; it is exactly the exclusive-create path.

What never happens is anything the app has to create:

  • bridge-state.json is unchanged since 2026-09-02 07:51. That timestamp is my

hand-edit to recover from the original failure — the app has not written the
file once in the 15 days since. This is why the "Connect new sessions to
Remote Control" toggle still resets on every restart.

  • claude-code-sessions\ contains exactly one session directory, created

2026-09-02, and nothing newer. No session state has been persisted in 15
days.

User-visible effect: no session window survives a restart. After a restart
the previous sessions are not reachable at all — there is nothing on disk to
restore them from, and nothing in the UI says so.

That is the claude-code-sessions\...\manifest.json line from the original
report, now with dates on it. It also shows how quiet the failure is: the app
looks healthy, keeps updating the files it already has, and silently drops
everything it would need to create.

Showing cached comments. Read the full discussion on GitHub ↗