[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)
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):
- Silent data misplacement — transcripts and resume state live where nothing looks.
--resumefrom other terminals, usage/cost tooling, and anything reading$CLAUDE_CONFIG_DIR/projectssees zero sessions — while the session itself appears to work perfectly, because it reads and writes the same wrong location consistently. - Privacy/leak hazard — I ended up with ~20MB of private conversation transcripts in a
~folder inside a git working tree, untracked and not gitignored — onegit add .away from being pushed. - Deletion hazard — a stray directory named
~invitesrm -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 whereCLAUDE_CONFIG_DIRis 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.
mkdir /tmp/tilde-repro && cd /tmp/tilde-reproenv 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)
ls -la→ a directory literally named~now exists in the cwd, containing the full config tree (projects/,sessions/,backups/,.claude.json)ls -d ~/.claude-tilde-test→No such file or directory— nothing was created at the intended home location- 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_plannedby stale-bot, unfixed, locked - #30794 — same root cause, plugin-marketplace variant; closed
not_plannedby stale-bot, unfixed - Open siblings of the same unexpanded-
~class: #69553 (UV_CACHE_DIRin 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.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗