[BUG] Write tool resolves absolute paths through `~/.claude` symlink to a literal-backslash-escaped path stored under cwd

Resolved 💬 3 comments Opened Apr 30, 2026 by 442991 Closed May 4, 2026

Severity: Medium (silent-rot class — write returns success, file lands at wrong path; recovery is straightforward via find + cp but discovery requires explicit verification because the success message itself doesn't reveal the bug).

Filing target: github.com/anthropics/claude-code — confirmed via package.json of the installed @anthropic-ai/claude-code@2.1.119 (homepage field: https://github.com/anthropics/claude-code). This is Claude Code core, distinct from #4's anthropics/claude-plugins-official (the plugin marketplace).

Title: Write tool resolves absolute paths through ~/.claude symlink to a literal-backslash-escaped path stored under cwd

Repro (deterministic, 6/6 confirmed independently tonight across 3 agents and both leaf + index file types):

  1. Agent has cwd at /Volumes/Jane-Data/claudeclaw/agents/<agent>/.
  2. Agent calls Write with file_path="/Users/jane/.claude/projects/-Volumes-Jane-Data-claudeclaw-agents-<agent>/memory/<filename>.md" (absolute path through ~/.claude, which on this system is a symlink to /Volumes/Jane-Data/claude-config).
  3. Write tool returns success with reported path: \/Volumes\/Jane-Data\/claude-config\/projects/-Volumes-Jane-Data-claudeclaw-agents-<agent>/memory/<filename>.md (forward slashes through the symlink target are backslash-escaped).
  4. The actual file lands at <cwd>/\/Volumes\/Jane-Data\/claude-config\/projects/-Volumes-Jane-Data-claudeclaw-agents-<agent>/memory/<filename>.md — a literal directory tree where the directory names contain backslash characters, prepended by cwd because the leading \/ is no longer recognized as an absolute-path-start.
  5. The intended absolute path (e.g. /Users/jane/.claude/projects/...) remains empty — read attempts via ls against the intended path show the file is not there.

Concrete incidents (independently observed, 6 of 6 hit the same pattern):

  • 2026-04-26 ~18:50 BST — Jane writing feedback_no_backticks_in_hcom_send.md to /Users/jane/.claude/projects/-Users-jane/memory/. Hit the escape-path bug.
  • 2026-04-26 18:51 BST — Bookworm writing feedback_push_peer_to_reclaim_canonical_name.md to /Users/jane/.claude/projects/-Volumes-Jane-Data-claudeclaw-agents-bookworm/memory/. Hit the escape-path bug.
  • 2026-04-26 18:51 BST — Bookworm's MEMORY.md (index file, not just leaf memory) write to the same directory. Hit the escape-path bug.
  • 2026-04-26 19:14 BST — Jane writing feedback_claim_proper_name_on_startup.md to her own memory dir. Hit the escape-path bug. Indexed in MEMORY.md as a phantom for ~25 min before audit.
  • 2026-04-26 ~19:50 BST — Guardian writing feedback_peer_restart_reclaim_nudge.md to /Users/jane/.claude/projects/-Volumes-Jane-Data-claudeclaw-agents-guardian/memory/. Hit the escape-path bug. Caught via post-write index-vs-files audit.
  • 2026-04-26 19:38 BST — Jane writing feedback_audit_after_memory_write.md (drafted via heredoc to bypass the bug). The heredoc path landed correctly; this is the negative-control confirming the bug is Write-tool-specific, not a filesystem issue.

All six writes succeeded as files on disk — the data isn't lost — but they landed at literal-backslash-escaped paths under their respective cwds rather than the intended ~/.claude/... absolute path. Six of six Write-tool calls through the symlink hit the same encoding pattern across 3 different agent cwds (Jane / Bookworm / Guardian) and across multiple file types (leaf memory *.md files + the MEMORY.md index file itself). Bash heredoc to the same path landed correctly (negative control). Appears deterministic for absolute paths whose first segment resolves through the /Users/jane/.claude → /Volumes/Jane-Data/claude-config symlink, specifically when invoked via the Write tool. Recovery is universal: find <cwd> -name <filename> + cp to intended path.

Workarounds in place (two paths):

  1. Heredoc bypass (preferred — ships clean on first attempt): use Bash heredoc directly instead of the Write tool: cat > /Users/jane/.claude/projects/.../foo.md << 'EOF' ... EOF. Bypasses the Write tool's path-resolution logic entirely; bash handles the symlinked path correctly. Bookworm shipped two memory writes via this path tonight, both landed at the intended absolute path on first attempt.
  1. Write-then-recover (fallback if heredoc not viable): after every Write to a ~/.claude/... path, verify the file landed at the intended absolute path via ls -la <intended-path>. If absent, run find <cwd> -name <filename> 2>/dev/null to locate the actual landing site, then cp to the intended path. Adds ~15 sec per write; not blocking but introduces silent-rot risk if the verification step is skipped (the Write tool's success message itself does NOT reveal the path-misdirection).

Expected: Write to /Users/jane/.claude/projects/.../foo.md lands at exactly that absolute path (or its symlink-resolved equivalent /Volumes/Jane-Data/claude-config/projects/.../foo.md); both ls /Users/jane/.claude/... and ls /Volumes/Jane-Data/claude-config/... should show the file.

Actual: file lands at <cwd>/\/Volumes\/Jane-Data\/claude-config\/projects/.../foo.md with backslash-escaped path components stored as literal directory names. Intended path remains empty.

Versions:

  • Claude Code core tool (Write). Version TBD — observed on the production version running on this system as of 2026-04-26 (Guardian to confirm exact version).
  • Bug observed on macOS Darwin 25.3.0 (M4 Mac Mini). Symlink topology: /Users/jane/.claude → /Volumes/Jane-Data/claude-config (via mklink-style link or ln -s).

Cross-class observation — silent-rot class member: this bug is a textbook silent-rot incident (per the umbrella addendum just shipped to Jane's lessons-learned tonight): tool returns success → file doesn't end up where expected → caller assumes operation worked → discovery requires explicit verification (find or ls). Same shape as Sheets API batch_update silently succeeding with wrong sheetId (Jane's lesson #13). The Write tool's failure mode would not surface through reactive error-handling — it requires the proactive read-back-after-mutation discipline. Worth Anthropic awareness: Write tool joins the Sheets API + iMessage MCP + jq schema-drift class as a silent-rot tool.

Likely sibling issues in the same path-resolution code (open as of filing):

  • #52463[BUG] Edit tool silently fails (reports success, no write) when target is a relative .. symlink inside a symlinked-directory chain. Same silent-failure family (tool reports success, write goes nowhere), different trigger (Edit + relative-..-leaf-symlink vs Write + absolute path through symlinked-directory). Fix probably touches the same symlink-resolution layer.
  • #45260[BUG] Standalone auto-updater writes to CWD instead of ~/.local/share/claude, creates relative symlink. Same wrong-path-CWD-fallback behavior we observe (intended absolute target → write actually lands under cwd). Different code path (updater vs Write tool) but identical failure shape.
  • #52772[BUG] Auto memory system prompt path uses CWD-based path but /memory creates directory at git-root-based path. Same area (auto-memory + path resolution + CWD divergence). Different symptom but adjacent code.

Triaging these together may surface a shared root cause in Claude Code's path-resolution / symlink-handling layer.

Asks (in preference order):

  1. Fix the path-resolution. Write should resolve ~/.claude symlinks normally (treating the resolved path as absolute), without backslash-escaping forward slashes in the resolved target. The fix likely sits at the symlink-handling layer of the path-resolution code.
  2. Surface the resolved path in the success message. Even after fix, returning the actual landing path (not the input path) on success would help callers verify correctly. Currently the success message echoes the input path, which masks the divergence.
  3. Fail-fast on backslash-escape detection. If the resolved path contains backslash-escaped path components (a strong sign of resolution failure), error rather than silently storing at the escaped-path location.

Logs/evidence pointers (local files, not for upload):

  • /Volumes/Jane-Data/claudeclaw/agents/bookworm/\/Volumes\/Jane-Data\/claude-config\/projects/-Volumes-Jane-Data-claudeclaw-agents-bookworm/memory/feedback_push_peer_to_reclaim_canonical_name.md — Bookworm's wrong-path file (kept temporarily as evidence; intended-path copy is canonical)
  • hcom thread: messages #34503 (watcher confirms allow-prompt resolved), #34528 (Bookworm flagging the bug post-recovery), #34535 (Jane confirming independent observation) — intra-team threading; not for upload

View original on GitHub ↗

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