[Bug] symlinkDirectories: writing to symlinked file replaces symlink with regular file

Resolved 💬 4 comments Opened Mar 30, 2026 by ymdvsymd Closed May 2, 2026

Description

When worktree.symlinkDirectories includes a file path (e.g., .claude/settings.local.json), Claude Code correctly creates a symlink at worktree creation time. However, when Claude Code later writes to that file (e.g., to persist a newly approved permission), the symlink is replaced with a regular file. This causes the worktree and the original repo to diverge silently.

Steps to Reproduce

  1. Configure worktree symlinks in .claude/settings.json:

``json
{
"worktree": {
"symlinkDirectories": [
".claude/settings.local.json",
".env",
"node_modules"
]
}
}
``

  1. Start a session with --worktree (or via EnterWorktree)
  2. Verify .claude/settings.local.json is a symlink → ✅
  3. During the session, approve a new permission (e.g., WebFetch(domain:linear.app) due to #9329)
  4. Claude Code writes the updated permission to .claude/settings.local.json
  5. Check .claude/settings.local.json again → no longer a symlink

Expected Behavior

Writing to a symlinked file should write through the symlink, updating the original file. The symlink should remain intact.

Actual Behavior

The symlink is replaced with a regular file. The original repo's settings.local.json is unchanged, while the worktree now has a diverged copy.

# After worktree creation (correct):
$ ls -la .claude/settings.local.json
lrwxr-xr-x  ...  .claude/settings.local.json -> /path/to/original/.claude/settings.local.json

# After Claude Code writes a new permission (broken):
$ ls -la .claude/settings.local.json
-rw-------  ...  .claude/settings.local.json    # regular file, symlink gone

Evidence: other symlinked paths (.env, .venv, node_modules) that Claude Code does not write to remain intact as symlinks.

Root Cause

Claude Code likely uses an atomic write pattern (write to temp file → rename to target path) when updating settings.local.json. The rename replaces the symlink itself rather than writing through it. This is a common pitfall with symlinks.

Suggested Fix

When writing to a file that is a symlink, either:

  1. Resolve the symlink first and write to the real path
  2. Write through the symlink (open with O_NOFOLLOW disabled, which is the default)
  3. After an atomic rename, detect that the original was a symlink and restore it

Related Issues

  • #40259 — another symlinkDirectories issue (cleanup failure, different root cause)
  • #9329 — WebFetch wildcard bug that triggers the extra permission write

Environment

  • macOS Darwin 24.6.0
  • Claude Code CLI (Opus 4.6)
  • Worktree created via EnterWorktree

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗