[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.
3 Comments
Confirmed — reproduced on 2.1.233 (Linux) following your exact steps:
env CLAUDE_CONFIG_DIR='~/.claude-tilde-test' claude -p "say ok"in a fresh directory creates a literal./~/.claude-tilde-test/in the cwd with the full config tree (sessions, transcripts, credentials included), creates nothing in the home directory, and prints no warning. The control run with an absolute path in the same directory works correctly, so it's specifically the unexpanded tilde being treated as a relative path.This is long-standing rather than a recent regression (prior reports of the same root cause date back many versions). Agreed the current silent fallback is the worst of the options — expanding a leading
~/or failing fast with a clear message are both reasonable, and either would also cover the sibling reports you linked. Flagging for a fix; the docs example that modelsCLAUDE_CONFIG_DIR=~/...deserves a caveat in the meantime.🤖 Generated with Claude Code
Thanks for confirming @bcherny, and good to know it reproduces on Linux, since triage had it labeled
platform:macosHappy to run any candidate build against the repro whenever there's something to test. I have the failing case and the absolute-path control scripted, and I can check the siblings (
plansDirectory, MCP stdio args, sandboxUV_CACHE_DIR) in the same pass to confirm one fix closes the family.If a docs caveat would help in the meantime, glad to draft the wording.
@preyashyadav Thanks for this, i fixed the issue for myself in my ubuntu this way
What we did was explicitly avoid the exact failure mode described in this GitHub issue.
The bug is: if
CLAUDE_CONFIG_DIRreaches Claude Code as a literal tilde path like:in a context where the shell does not expand
~, Claude can wrongly treat it as a relative path and create a literal~/directory inside the current project. That can misplace transcripts, session history, backups, and even credentials. ([GitHub][1])We solved it by always using the full absolute path:
instead of:
We also put that absolute path in my Account 2 VS Code settings:
and in your launcher:
So even when VS Code passes the environment variable directly to Claude Code, there is no
~left for Claude Code to misinterpret.The issue itself confirms that repeating the same test with an absolute path works correctly, while a literal
~can create the bad directory inside the current working directory. ([GitHub][1])So my current setup specifically protects against issue #78988.
One useful final check is:
Ideally, that returns nothing.
And inside Account 2 VS Code:
should continue to return:
That is the key fix.