[BUG] `CLAUDE_CONFIG_DIR` containing `~` is treated as a cwd-relative path — entire config tree (transcripts, sessions, history) silently written to a literal `./~/` directory inside the project (repros on latest 2.1.214)

Status Open
Reported on v2.1.212
Maintainer reply None cached
Activity 2 comments · opened Jul 19, 2026

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?

When CLAUDE_CONFIG_DIR reaches Claude Code containing a literal ~ (set through any channel where a shell never expands it — VS Code terminal.integrated.env, launchd, devcontainer/CI env blocks, .env parsers), Claude Code does not expand the tilde and does not error. It treats the value as a relative path from the current working directory and silently creates a directory literally named ~ inside the project, then writes its entire config tree there: projects/ (session transcript JSONL), sessions/, backups/, .claude.json, history.

Real-world impact (this ran for days on my machine before I found it):

  1. Silent data misplacement — transcripts and resume state live where nothing looks. --resume from other terminals, usage/cost tooling, and anything reading $CLAUDE_CONFIG_DIR/projects sees zero sessions — while the session itself appears to work perfectly, because it reads and writes the same wrong location consistently.
  2. Privacy/leak hazard — I ended up with ~20MB of private conversation transcripts in a ~ folder inside a git working tree, untracked and not gitignored — one git add . away from being pushed.
  3. Deletion hazard — a stray directory named ~ invites rm -rf ~ without quotes, which destroys the user's home directory.

Note: this exact root cause was reported twice before (#37570 credentials variant, #30794 plugin-marketplace variant). Both were auto-closed by the stale-bot as not_planned without a fix and are now locked; #37570's closing message says to file a new issue and reference it — which this is. It still reproduces on the latest release.

What Should Happen?

Either behavior is fine; the current silent fallback is not:

  • Expand a leading ~/~/ to the home directory at the single point where CLAUDE_CONFIG_DIR is read (the convention git/ssh follow for user-facing path config), or
  • Fail fast with a clear error, e.g. CLAUDE_CONFIG_DIR contains an unexpanded ~ — use an absolute path.

Never fall through to interpreting ~/... as a relative directory literally named ~. Fixing it at the shared path-resolution point would also close the sibling reports (#69553, #68175, #37580).

Error Messages/Logs

None — that is the core of the bug: no error, no warning. Terminal evidence instead:


$ mkdir /tmp/tilde-repro && cd /tmp/tilde-repro
$ env CLAUDE_CONFIG_DIR='~/.claude-tilde-test' claude -p "say ok"
Not logged in · Please run /login
$ ls -la
drwxr-xr-x  ~          <-- literal directory named "~" created in cwd
$ find ./~ -maxdepth 2
./~/.claude-tilde-test
./~/.claude-tilde-test/projects
./~/.claude-tilde-test/sessions
./~/.claude-tilde-test/.claude.json
./~/.claude-tilde-test/backups
$ ls -d ~/.claude-tilde-test
ls: /Users/<me>/.claude-tilde-test: No such file or directory

Steps to Reproduce

No login required — the misplaced tree is created on any invocation.

  1. mkdir /tmp/tilde-repro && cd /tmp/tilde-repro
  2. env CLAUDE_CONFIG_DIR='~/.claude-tilde-test' claude -p "say ok"

(the single quotes are the point — they deliver the literal, unexpanded value exactly as VS Code's terminal.integrated.env, launchd, or a CI env block would; shells normally expand ~ before Claude ever sees it, which is why export CLAUDE_CONFIG_DIR=~/foo in .zshrc masks the bug)

  1. ls -la → a directory literally named ~ now exists in the cwd, containing the full config tree (projects/, sessions/, backups/, .claude.json)
  2. ls -d ~/.claude-tilde-testNo such file or directory — nothing was created at the intended home location
  3. Control (proves it's only the tilde): repeat step 2 with an absolute path — env CLAUDE_CONFIG_DIR=/Users/<you>/.claude-tilde-test claude -p "x" — and the config dir is correctly created in the home directory. Same binary, same cwd.

Claude Model

None

Is this a regression?

I don't know

Last Working Version

Unknown / possibly never — prior reports of the same root cause date back months (#30794 observed on v2.1.68, #37570 filed 2026-03), so this appears long-standing rather than a recent regression. Personally confirmed on 2.1.212 and 2.1.214.

Claude Code Version

2.1.214 (Claude Code)

Platform

Other

Operating System

macOS

Terminal/Shell

VS Code integrated terminal

Additional Information

<img width="2040" height="646" alt="Image" src="https://github.com/user-attachments/assets/94e95318-4052-4db3-b14e-b34322b11234" />

Screenshot attached: VS Code + terminal side by side — claude --version → 2.1.214, bare ls showing the ~ directory in a fresh test folder, and the Explorer showing the full config tree inside it including a real session transcript JSONL.

Docs note: the official CLAUDE_CONFIG_DIR documentation (code.claude.com/docs/en/env-vars) itself models tilde usage — alias claude-work='CLAUDE_CONFIG_DIR=~/.claude-work claude'. That example only works because a shell happens to expand it; nothing warns that non-shell contexts won't, so the docs actively invite this failure.

<img width="3024" height="821" alt="Image" src="https://github.com/user-attachments/assets/128f41d9-bf6a-4a4d-9c52-743c8270b5ee" />

https://code.claude.com/docs/en/env-vars#:~:text=CLAUDE_CONFIG_DIR,claude%2Dwork%20claude%27

Prior & related issues:

  • #37570 — same root cause, credentials variant; closed not_planned by stale-bot, unfixed, locked
  • #30794 — same root cause, plugin-marketplace variant; closed not_planned by stale-bot, unfixed
  • Open siblings of the same unexpanded-~ class: #69553 (UV_CACHE_DIR in sandbox), #68175 (plansDirectory), #37580 (MCP stdio args)
  • Possibly related, different mechanism: #78843 (custom CLAUDE_CONFIG_DIR, interactive transcripts not written, Linux)

Contribution offer: the repro is minimal and deterministic and I'm glad to test any candidate build. If external contributions are possible anywhere here, I'd happily submit the fix — it's a one-point change (expand-or-reject at the CLAUDE_CONFIG_DIR read site), and fixing it there closes the whole family above.

View original on GitHub ↗

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