Windows: app state silently lost when %APPDATA%\Claude is a junction — data directory not configurable
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%\Claudeis a directory junction toC:\Gehirn\.claude\programmdaten,
created 2026-08-28 09:19:14
Symptoms
- First failure in
main.logat 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
remoteSessionIdcould not be cleared, producing
404 Failed to connect transport 78 times, so existing windows became unreachable.
- Recovery required hand-editing
bridge-state.jsonoutside 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-dirworks (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
- 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.
- Handle
EEXISTon 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.
- 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.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
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.tmpwithO_CREAT|O_EXCL(CREATE_NEW), then rename over the target". The create half always targets the private redirection store. When%APPDATA%\Claudeis a reparse point (junction or symlink), the redirection cannot mirror that directory, and the create fails immediately withEEXIST— 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):...\LocalCache\Roaming\Claude-3p, but no...\LocalCache\Roaming\Claudeat all — the redirection for the junctioned folder never materialises..tmpfiles exist, in the private store or anywhere on the disk, so this is not a leftover-file or race problem.EEXISTin 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
unvirtualizedResourcesrestricted capability plus eitheror, better, the granular Windows 11 form:
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
EEXISTinstead of silently giving up, and surface state-save failures in the UI rather than only inmain.log. A user whose settings silently fail to persist across restarts currently has no way to know why.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 against1.40609.1.0.
%APPDATA%\Claudeis still a junction toC:\Gehirn\.claude\programmdaten.Re-ran the probe from the original report, unchanged, directly in
%APPDATA%\Claude— 50 attempts:ok=0, EEXIST=50. Exclusive create stillfails 100% of the time through the reparse point, and no stale
.tmpfile isleft 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.
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.jsonandgit-worktrees.jsonat 10:04,
buddy-tokens.jsonat 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.jsonis unchanged since 2026-09-02 07:51. That timestamp is myhand-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, created2026-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.jsonline from the originalreport, 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.