Where Claude Code Keeps Its Config, and How to Move It with CLAUDE_CONFIG_DIR
📅 October 13, 2026 · ⏱️ 14 min read
Claude Code keeps your user configuration in ~/.claude in your home directory: your settings.json, your own CLAUDE.md, plugins, session transcripts, prompt history and auto memory. Beside that folder, not inside it, it keeps one more file, ~/.claude.json, with its own state: your sign-in, your user and local MCP servers, and per-project trust. Set CLAUDE_CONFIG_DIR to an absolute path and Claude Code uses that directory instead of ~/.claude. The environment variables page of the docs, read on 2026-10-13, says it does this: “Override the configuration directory (default: ~/.claude). All settings, session history, and plugins are stored under this path.” Run here, setting it in the shell also moved .claude.json: the file went inside the new directory, and claude mcp add wrote nothing at all under HOME. It does not move a project’s own .claude/ folder, CLAUDE.md or .mcp.json, and it does not move managed settings.
That is the short answer. The rest of this page is the docs’ map of what lives where, then five things run on this machine, where claude --version printed 2.1.296 (Claude Code). Every command below ran in a shell where S is an empty scratch directory, HOME and CLAUDE_CONFIG_DIR already point inside it, and CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC and DISABLE_TELEMETRY are both 1. Each command then sets its own HOME and CLAUDE_CONFIG_DIR on the same line, so you can read each case on its own. The commands ran in a network namespace with no network interface up, so none of them could reach the internet. No Claude Code session was started, no model was called, and nobody was signed in. The one thing that needs a session, the alias example further down, is marked as not run.
claude --version
2.1.296 (Claude Code)
Where each thing lives
The docs page that maps the folder is Explore the .claude directory. Its rule for the variable is one sentence: “If you set CLAUDE_CONFIG_DIR, every ~/.claude path on this page lives under that directory instead.” It also says where the folder sits by default: “Claude Code reads instructions, settings, skills, subagents, and memory from your project directory and from ~/.claude in your home directory.”
The table uses that page, plus the settings and memory pages where it links out to them. It gives each item’s default location, and the last column is what the docs say CLAUDE_CONFIG_DIR does to each one. The .claude.json row is the one place the docs don’t say, so that row gives what happened here instead.
| What | Where | Docs | Moved? |
|---|---|---|---|
| Your settings | ~/.claude/settings.json | Applies to “You, in every project on this machine” | Moves: it is a ~/.claude path |
| Your instructions | ~/.claude/CLAUDE.md | ”Personal preferences for all projects” | Moves |
| Auto memory | memory/ in projects/<project>/, under ~/.claude | ”Each project gets its own memory directory” | Moves |
| Session transcripts | <session>.jsonl in projects/<project>/, under ~/.claude | ”Full conversation transcript: every message, tool call, and tool result” | Moves |
| Prompt history | history.jsonl under ~/.claude | ”Every prompt you’ve typed, with timestamp and project path.” | Moves |
| Plugins | ~/.claude/plugins | ”Cloned marketplaces, installed plugin versions,“ | Moves |
| Login credentials file | ~/.claude/.credentials.json | ”your login credentials” | Moves |
| Claude Code’s own state | ~/.claude.json, in HOME, not in ~/.claude | ”Claude Code also keeps a fifth file, ~/.claude.json, that it writes for itself; you don’t need to edit it.” | The docs don’t say. Here it moved inside the directory, as .claude.json (case B) |
| Old copies of that file | backups/ under ~/.claude | ”Earlier versions of ~/.claude.json, copied when Claude Code rewrites the file.” | Moves |
| Project files | In the repository: .claude/, CLAUDE.md, .mcp.json | ”Project-scope files live in your repo under .claude/” | Stay in the repository |
| Managed settings | managed-settings.json | ”System-level, varies by OS” | Stay where your organization put them |
Two rows need a word more. The docs list .credentials.json as “your login credentials”, and the authentication page says the variable carries it along: “If you’ve set the CLAUDE_CONFIG_DIR environment variable, Claude Code keeps the .credentials.json file under that directory instead”. That is what makes two logins side by side possible. And the second docs quote for ~/.claude.json says what is in it: “It holds your sign-in session, MCP server configurations, per-project state such as trust decisions, and the global config keys that /config writes for you.” Losing track of that file means losing your MCP servers, which is why the cases below watch it.
Which settings file wins when two of them set the same key is a different question, and it has its own post on settings.json.
How the cases were run
Each case runs one command that writes to .claude.json, claude mcp add --scope user, with a do-nothing server, /bin/true. The docs give the form as claude mcp add [options] <name> -- <command> [args...], and say “User-scoped servers are stored in ~/.claude.json and provide cross-project accessibility”. So that file is where the server has to land. A find before and after shows every file the command created. The scopes and the JSON that claude mcp add writes are covered in the post on adding an MCP server.
Claude Code also makes a dated copy of the file each time it rewrites it, and the docs say where the copies go: “Earlier versions of ~/.claude.json, copied when Claude Code rewrites the file. Claude Code keeps the five newest, plus a copy of any version it couldn’t parse.” The number at the end of each backup name below is a timestamp in milliseconds, so it is different on every run.
Case A: the variable unset
This is a normal install. env -u CLAUDE_CONFIG_DIR removes the variable for that one command, and HOME points at an empty folder.
mkdir -p "$S/a"
echo "before:"; find "$S/a" -type f | sort
env -u CLAUDE_CONFIG_DIR HOME="$S/a" claude mcp add --scope user demo -- /bin/true; echo "exit: $?"
echo "after:"; find "$S/a" -type f | sort
before:
Added stdio MCP server demo with command: /bin/true to user config
File modified: /home/loopdev/dankdev/node_modules/.scratch/s/a/.claude.json
exit: 0
after:
/home/loopdev/dankdev/node_modules/.scratch/s/a/.claude.json
/home/loopdev/dankdev/node_modules/.scratch/s/a/.claude/backups/.claude.json.backup.1791909598797
Both files are where the docs put them. The main file is $S/a/.claude.json, at the top of HOME, next to the .claude folder and not inside it. The backup is $S/a/.claude/backups/, inside the folder. before: printed nothing because the folder was empty.
Case B: the variable set in the shell
Same command, with CLAUDE_CONFIG_DIR pointing at a second empty folder.
mkdir -p "$S/b" "$S/b-config"
echo "before:"; find "$S/b" "$S/b-config" -type f | sort
HOME="$S/b" CLAUDE_CONFIG_DIR="$S/b-config" claude mcp add --scope user demo -- /bin/true; echo "exit: $?"
echo "after:"; find "$S/b" "$S/b-config" -type f | sort
before:
Added stdio MCP server demo with command: /bin/true to user config
File modified: /home/loopdev/dankdev/node_modules/.scratch/s/b-config/.claude.json
exit: 0
after:
/home/loopdev/dankdev/node_modules/.scratch/s/b-config/.claude.json
/home/loopdev/dankdev/node_modules/.scratch/s/b-config/backups/.claude.json.backup.1791909598992
Everything went into the config directory, and nothing was written under HOME ($S/b). The main file is $S/b-config/.claude.json and the backup is $S/b-config/backups/. The main file did not stay beside the folder as it does in case A. It moved into it, under the same name with its leading dot.
This matters if you move an existing setup. Copying ~/.claude to the new place is not enough, because your MCP servers, sign-in and project trust are in ~/.claude.json, which sits outside that folder. Here, Claude Code with the variable set read and wrote .claude.json inside the new directory (case B above, and the two claude mcp list runs further down). So the file you carry over goes in there, as .claude.json.
Case C: set only in settings.json, where it half works
The docs say you can set the variable in a settings file: “Set it in your shell, user settings, or managed settings.” They add one condition for settings files, because no shell expands the value: “For a variable that takes a path, such as CLAUDE_CONFIG_DIR, write the absolute path”. The env key is the settings key for it: “Set environment variables for every session and for the subprocesses Claude Code starts from it.”
So case C unsets the variable in the shell, as in case A, and puts it in the env block of the user settings.json instead, pointing at $S/c-other.
mkdir -p "$S/c/.claude" "$S/c-other"
printf '{ "env": { "CLAUDE_CONFIG_DIR": "%s" } }\n' "$S/c-other" > "$S/c/.claude/settings.json"
cat "$S/c/.claude/settings.json"
echo "before:"; find "$S/c" "$S/c-other" -type f | sort
env -u CLAUDE_CONFIG_DIR HOME="$S/c" claude mcp add --scope user demo -- /bin/true; echo "exit: $?"
echo "after:"; find "$S/c" "$S/c-other" -type f | sort
{ "env": { "CLAUDE_CONFIG_DIR": "/home/loopdev/dankdev/node_modules/.scratch/s/c-other" } }
before:
/home/loopdev/dankdev/node_modules/.scratch/s/c/.claude/settings.json
Added stdio MCP server demo with command: /bin/true to user config
File modified: /home/loopdev/dankdev/node_modules/.scratch/s/c/.claude.json
exit: 0
after:
/home/loopdev/dankdev/node_modules/.scratch/s/c-other/backups/.claude.json.backup.1791909599192
/home/loopdev/dankdev/node_modules/.scratch/s/c/.claude.json
/home/loopdev/dankdev/node_modules/.scratch/s/c/.claude/settings.json
The two files went to different places. With CLAUDE_CONFIG_DIR set only in the env block of the user settings.json, claude mcp add wrote the main file to $S/c/.claude.json, in HOME, as if the variable were unset, and wrote its backup to $S/c-other/backups/, the directory the variable names. Listing the servers with the same setup still finds the server, so Claude Code reads the main file from HOME too:
env -u CLAUDE_CONFIG_DIR HOME="$S/c" claude mcp list; echo "exit: $?"
Checking MCP server health…
demo: /bin/true - ✘ Failed to connect — CONNECTION_CLOSED: Connection closed
exit: 0
✘ Failed to connect is expected here. claude mcp list starts each server to check it, and the docs say it “shows a health status next to each server it lists, such as ✔ Connected, ! Needs authentication, or ✘ Failed to connect.” /bin/true exits at once, so there is nothing to connect to.
The docs have a section called Variables Claude Code ignores in env, and CLAUDE_CONFIG_DIR is on it, but that section is about project and local settings, not your user file. It lists “Variables that choose where Claude Code stores or writes its own files: CLAUDE_CONFIG_DIR, CLAUDE_CODE_TMPDIR, and the operating-system directory variables such as HOME, TMPDIR, TMP, TEMP, and the XDG_* family.” The reason it gives is about repositories: “Project and local settings can’t set variables that a checked-out repository shouldn’t control; set those in your shell, user settings, or managed settings instead.” For user settings the docs only say when the value applies: “From user settings, --settings, and managed settings: at startup, and again in the running session when a saved change alters the merged env.” Nothing in the docs explains why the backup moved and the main file did not. On 2.1.296, if you want everything moved, set the variable in your shell or in an alias, not in settings.json.
Two config directories side by side
Running two accounts is the use the docs give for the variable: “Useful for running multiple accounts side by side: for example, alias claude-work='CLAUDE_CONFIG_DIR=~/.claude-work claude'.” The authentication page adds: “Each directory has its own settings, session history, and claude.ai login or API key.” In a shell alias the ~ is expanded by the shell, so the docs’ form works there. In a settings file it would not be: “No shell processes them, so shorthand such as ~ or $HOME stays as typed.”
Not run here: the second line starts an interactive Claude Code session, and nothing on this page starts one. The first line is the docs’ own example, copied as written.
alias claude-work='CLAUDE_CONFIG_DIR=~/.claude-work claude'
claude-work
What can be run without a session is whether the two directories see each other’s MCP servers. One HOME, two config directories, one server added to each:
mkdir -p "$S/d"
HOME="$S/d" CLAUDE_CONFIG_DIR="$S/d/.claude-personal" claude mcp add --scope user personal-tools -- /bin/true; echo "exit: $?"
HOME="$S/d" CLAUDE_CONFIG_DIR="$S/d/.claude-work" claude mcp add --scope user work-tools -- /bin/true; echo "exit: $?"
Added stdio MCP server personal-tools with command: /bin/true to user config
File modified: /home/loopdev/dankdev/node_modules/.scratch/s/d/.claude-personal/.claude.json
exit: 0
Added stdio MCP server work-tools with command: /bin/true to user config
File modified: /home/loopdev/dankdev/node_modules/.scratch/s/d/.claude-work/.claude.json
exit: 0
Then the list from each one:
HOME="$S/d" CLAUDE_CONFIG_DIR="$S/d/.claude-personal" claude mcp list; echo "exit: $?"
HOME="$S/d" CLAUDE_CONFIG_DIR="$S/d/.claude-work" claude mcp list; echo "exit: $?"
find "$S/d" -type f | sort
Checking MCP server health…
personal-tools: /bin/true - ✘ Failed to connect — CONNECTION_CLOSED: Connection closed
exit: 0
Checking MCP server health…
work-tools: /bin/true - ✘ Failed to connect — CONNECTION_CLOSED: Connection closed
exit: 0
/home/loopdev/dankdev/node_modules/.scratch/s/d/.cache/claude-cli-nodejs/-home-loopdev-dankdev-node-modules--scratch-s/mcp-logs-personal-tools/2026-10-13T16-40-00-063Z.jsonl
/home/loopdev/dankdev/node_modules/.scratch/s/d/.cache/claude-cli-nodejs/-home-loopdev-dankdev-node-modules--scratch-s/mcp-logs-work-tools/2026-10-13T16-40-00-299Z.jsonl
/home/loopdev/dankdev/node_modules/.scratch/s/d/.claude-personal/.claude.json
/home/loopdev/dankdev/node_modules/.scratch/s/d/.claude-personal/backups/.claude.json.backup.1791909599637
/home/loopdev/dankdev/node_modules/.scratch/s/d/.claude-work/.claude.json
/home/loopdev/dankdev/node_modules/.scratch/s/d/.claude-work/backups/.claude.json.backup.1791909599825
Each directory lists only its own server: personal-tools in .claude-personal, work-tools in .claude-work. Each has its own .claude.json and its own backups/.
The find also shows something the variable does not move. claude mcp list wrote a log for each server under $S/d/.cache/claude-cli-nodejs/, in HOME, not in either config directory. That is a cache, not configuration, but it means a moved config directory does not keep everything Claude Code writes in one place, and two accounts that share a HOME share that folder.
claude doctor under a moved directory
claude doctor checks settings without starting a session. The CLI reference describes it as: “Print read-only installation and settings diagnostics from the terminal without starting a session, including install health, settings-file validation errors, and Remote Control eligibility.” To see which user settings.json it reads, this block puts an invalid one, a number where model takes a string, in the work directory only. Then it runs doctor once for each directory.
printf '%s\n' '{ "model": 42 }' > "$S/d/.claude-work/settings.json"
HOME="$S/d" CLAUDE_CONFIG_DIR="$S/d/.claude-work" claude doctor </dev/null > work.txt; echo "exit: $?"
HOME="$S/d" CLAUDE_CONFIG_DIR="$S/d/.claude-personal" claude doctor </dev/null > personal.txt; echo "exit: $?"
grep -A1 '^Invalid settings' work.txt personal.txt; echo "grep exit: $?"
ls -A "$S/d"
exit: 0
exit: 0
work.txt:Invalid settings
work.txt-- /home/loopdev/dankdev/node_modules/.scratch/s/d/.claude-work/settings.json › model: Expected string, but received number
grep exit: 0
.cache
.claude-personal
.claude-work
.npm
Doctor checked the settings.json in the directory CLAUDE_CONFIG_DIR named. It flagged $S/d/.claude-work/settings.json, and the run for .claude-personal found nothing wrong, because that directory has no settings file. Both runs exited 0 even with the invalid file, so in a script, check doctor’s report for an Invalid settings line, not its exit code. The last ls shows .npm in HOME next to .cache. It was not there after the claude mcp list runs, so the two doctor runs created it, in HOME, not in the config directory.
What CLAUDE_CONFIG_DIR does not move
- A project’s
.claude/folder,CLAUDE.mdand.mcp.json. They stay in the repository, by the docs’ table above. - Managed settings, which the docs describe as “System-level, varies by OS”.
- The MCP logs under
$S/d/.cache/claude-cli-nodejs/and the.npmfolder thatclaude doctorleft. Both stayed inHOMEon 2.1.296. .claude.jsonitself, when the variable is set only in the usersettings.json(case C). Only its backup moved.
The variable also cannot be set from a repository: the docs’ table for it ends “Ignored in project and local settings”.
Follow new posts by RSS: paste dankdev.com/feed.xml into any feed reader. It is a plain XML file — no signup, no email address, no account. Posts land there the same day they go up, and there is nothing to unsubscribe from later.