[BUG] Claude Desktop replaces symlinked claude_desktop_config.json with a regular file on save (atomic write is not symlink-aware)
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?
Claude Desktop saves ~/.config/Claude/claude_desktop_config.json with a non-symlink-aware atomic write (write a temp file, then rename() it over the target path). When that path is a symlink — a common dotfiles setup where the config is symlinked into a version-controlled repo — the rename() replaces the symlink itself with a brand-new regular file instead of writing through the link to its target. The symlink is silently destroyed on the first settings change, so the file is no longer linked into the repo and further edits stop syncing.
What Should Happen?
Saving the config should preserve a symlink at that path. A symlink-aware atomic write resolves the real target first (realpath), writes the temp file in the target's directory, and renames onto the resolved target — so the symlink at ~/.config/Claude/claude_desktop_config.json is kept and points at the updated file. (This is exactly what the widely used write-file-atomic npm package does by default.)
Error Messages/Logs
# No error is emitted — the symlink is replaced silently. Evidence via stat before/after a settings change:
# Before (managed symlink):
$ ls -la ~/.config/Claude/claude_desktop_config.json
lrwxrwxrwx ... claude_desktop_config.json -> ../../dotfiles/.../claude_desktop_config.json
# After changing any setting in the app:
$ stat -c 'type=%F inode=%i' ~/.config/Claude/claude_desktop_config.json
type=regular file inode=92166112 # new inode; was 92165154 (the symlink target) — proof of temp-file + rename
Steps to Reproduce
- Quit Claude Desktop.
- Move the config into another directory and symlink it back, e.g.:
``bash``
mv ~/.config/Claude/claude_desktop_config.json ~/some-repo/claude_desktop_config.json
ln -s ~/some-repo/claude_desktop_config.json ~/.config/Claude/claude_desktop_config.json
- Start Claude Desktop and change any setting (e.g. toggle a preference), so the app saves the config.
- Inspect the path:
ls -la ~/.config/Claude/claude_desktop_config.json— it is now a regular file, not a symlink, and the repo copy no longer receives updates.
Claude Model
Not sure / Multiple models
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
Claude Desktop app, version 1.22209.3
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Other
Additional Information
The atomic-write pattern itself is correct and desirable (it prevents a corrupted/half-written config on crash) — the gap is only that it doesn't resolve symlinks before writing. Note this fix would preserve symlinks specifically; a hardlink cannot survive a rename() regardless. Supporting symlinked config would let users manage claude_desktop_config.json with dotfiles managers (GNU Stow, chezmoi, bare-repo setups, etc.), which is currently impossible because every save breaks the link.
Related issues (same root cause, different files — this appears to be a systemic pattern across config writers, so a shared symlink-aware atomic-write fix would address all of them): #76561 reports the identical mechanism for ~/.claude/.credentials.json ("credential writes replace symlinks with private regular files… rename() … never follows a symlink"); #78162 is a related symlink-aware atomic-write problem for ~/.claude/settings.json (resolves the symlink only one hop instead of full realpath) and proposes the same realpath fix; #67208 (closed) was an earlier settings.json symlink mis-resolution. This report is the Claude Desktop claude_desktop_config.json instance of the same class of bug.
Suggested fixes
- Make the config writer symlink-aware. Before the atomic write, resolve the path with
fs.realpath(or equivalent); write the temp file in the resolved target's directory (same filesystem, sorename()stays atomic) and rename onto the resolved target. The symlink at the original path is preserved. - If already using
write-file-atomic, ensure its default symlink resolution is not disabled (some wrappers pass an option or pre-resolve to the link path, which reintroduces this bug). - At minimum, document the limitation and/or detect when the target is a symlink and preserve it, so dotfiles users aren't silently surprised.