上一篇挪 Codex,改个 CODEX_HOME 就完事了。Claude Code 我以为是同一件事重做一遍,结果多花了一个多小时——环境变量设完,插件一个都没加载出来。
下面是完整的六步,按顺序做。第三步是上一篇没有的坑,跳过它插件必然失效。
先看清楚目录里有什么
.claude 里这几样必须保住:
projects/— 所有对话历史,按项目路径分目录存.jsonlplugins/— 插件本体、缓存和元数据skills/— 技能settings.json— 全局设置,含插件启用列表.credentials.json— 登录凭据
还有一个不在这个目录里:~/.claude.json。它躺在用户主目录根下,跟 .claude 平级,存项目索引和 MCP 服务器配置,我这边 150 KB。
我这份 .claude 有 8.6 GB、7 万多个文件,大头全在 plugins/cache。装了三十多个插件,每个是一次 git clone,缓存就这么堆起来的。
第一步:复制目录
robocopy "$env:USERPROFILE\.claude" "D:\Claude\.claude" /E /COPY:DAT /R:1 /W:1 /XJ
/XJ 跳过 junction 和符号链接。plugins/cache 底下会有指向别处的链接,不跳过 robocopy 有概率顺着爬出去,甚至绕回来死循环。
/COPY:DAT 而不是上一篇用的 /COPYALL。/COPYALL 要连 ACL 和审计信息一起复制,非管理员身份跑会大面积报权限错误。数据、属性、时间戳三样够用。
跑了 3 分 59 秒,71166 个文件,退出码 9。
退出码 9 看着像失败,其实是位标志相加:1 表示有文件被复制,8 表示有文件没复制成功。真正失败的是两个:
ERROR 2 (0x00000002) Accessing Destination Directory
...\plugins\cache\temp_git_1784715795236_nimcz8\
ERROR 2 (0x00000002) Copying File
...\telemetry\1p_failed_events.<session-id>.json
一个是 git clone 留下的临时目录,一个是当前会话正在写的 telemetry 文件,都是运行时垃圾,边复制边消失。robocopy 退出码小于 8 就是干净成功,等于 8 或 9 要看一眼错误行是什么,别看见非零就重跑。
第二步:补上目录外面的那个文件
Copy-Item "$env:USERPROFILE\.claude.json" "D:\Claude\.claude\.claude.json" -Force
只 copy .claude 会漏掉它。设了 CLAUDE_CONFIG_DIR 之后,Claude Code 会去配置目录里找 .claude.json,找不到就当成全新安装,项目索引和 MCP 配置全丢。
第三步:改掉插件元数据里的绝对路径
这一步是整件事的关键。plugins/ 下有两个 JSON 文件,里面存的是绝对路径:
installed_plugins.json,每个插件一条:
"superpowers@claude-plugins-official": [
{
"scope": "user",
"installPath": "C:\\Users\\<user>\\.claude\\plugins\\cache\\claude-plugins-official\\superpowers\\6.1.1",
"version": "6.1.1"
}
]
known_marketplaces.json,每个市场一条:
"claude-plugins-official": {
"source": { "source": "github", "repo": "anthropics/claude-plugins-official" },
"installLocation": "C:\\Users\\<user>\\.claude\\plugins\\marketplaces\\claude-plugins-official"
}
robocopy 只搬文件,不改内容。文件到了 D 盘,路径还写着 C 盘。我这边表现是:/plugin 列表里空空如也,skills 只剩内置的那几个,settings.json 里 enabledPlugins 三十多条一条没生效。因为源目录我留着没删,路径本身不算失效,所以也没有任何报错。
批量改写,先备份:
$base = "D:\Claude\.claude\plugins"
Copy-Item "$base\installed_plugins.json" "$base\installed_plugins.json.bak" -Force
Copy-Item "$base\known_marketplaces.json" "$base\known_marketplaces.json.bak" -Force
foreach ($f in @("$base\installed_plugins.json", "$base\known_marketplaces.json")) {
$s = Get-Content $f -Raw
$s = $s.Replace('C:\\Users\\<user>\\.claude', 'D:\\Claude\\.claude')
[IO.File]::WriteAllText($f, $s, (New-Object Text.UTF8Encoding $false))
}
两个细节。JSON 里的反斜杠是转义过的,所以匹配串要写双反斜杠。写回用 WriteAllText 配无 BOM 的 UTF8Encoding,别用 Set-Content——它默认按系统 ANSI 编码写,中文路径会变乱码。
改完确认一遍每条路径都能落地:
$j = Get-Content "D:\Claude\.claude\plugins\installed_plugins.json" -Raw | ConvertFrom-Json
foreach ($p in $j.plugins.PSObject.Properties) {
if (-not (Test-Path $p.Value[0].installPath)) { "MISSING: $($p.Name)" }
}
没有输出就是全通。
~/.claude.json 里可能也有一条 C:/Users/<user>/.claude,注意看它在哪个字段。我那条在 projects 下,是「这个目录被当项目打开过」的索引记录,不是插件路径,别顺手改掉。
第四步:设环境变量
[Environment]::SetEnvironmentVariable("CLAUDE_CONFIG_DIR", "D:\Claude\.claude", "User")
Machine 级不用设。环境变量解析是 User 覆盖 Machine,你自己的终端永远读 User 那份。机器级只在「这台机器上别的 Windows 账户也要用同一份配置」时才有意义,而那意味着共用同一份 projects/ 和同一份凭据,多数情况下不是你想要的。
真要设也别指望在 Claude Code 里让它代跑。写机器级变量要动 HKLM,需要管理员权限,而 Claude Code 开的是非交互会话,UAC 弹窗弹不出来。我试了两次 Start-Process -Verb RunAs,命令返回正常,回头查注册表是空的——静默失败,没有报错。要设就手动开个管理员 PowerShell。
第五步:重启,然后更新插件
改完开新终端,旧终端读的还是老值。
新终端里先确认 CLI 能正常枚举:
claude plugin list
能列出插件、状态显示 enabled,说明第三步的路径改对了。
接着把市场和插件都刷一遍。市场元数据里的 commit SHA 是迁移前的,不更新的话下次自动检查更新可能对不上:
claude plugin marketplace update
然后逐个更新插件。claude plugin update 一次只吃一个插件名,三十多个手敲不现实,从 installed_plugins.json 里读出来循环:
$j = Get-Content "D:\Claude\.claude\plugins\installed_plugins.json" -Raw | ConvertFrom-Json
$fail = @()
foreach ($n in $j.plugins.PSObject.Properties.Name) {
claude plugin update $n | Out-Null
if ($LASTEXITCODE -ne 0) { $fail += $n }
}
"failed: $($fail.Count)"; $fail
我这边 9 个市场、38 个插件,全部成功。跑完还得再重启一次,plugin update 的结果下次启动才应用。
第六步:验证
Test-Path "D:\Claude\.claude\.credentials.json"
(Get-ChildItem "D:\Claude\.claude\projects").Count
我这边 projects 下 32 个目录。进 Claude Code 跑一次 /resume,历史对话能翻出来;/plugin 里插件都在,状态 enabled;/skills 能看到插件带的技能。三样都对就算成了。
源目录先别删,确认几天再清理。C 盘也不差这几天。
顺手翻了一下别的环境变量
既然在折腾配置,把 Claude Code 认哪些环境变量都摸了一遍。官方文档有完整列表,但页面很长,抓取容易被截断。
更直接的办法是从本机装的二进制里扒。Claude Code 现在发的是打包好的原生二进制,JS 源码带字符串留在里面:
$p = "$env:USERPROFILE\.local\share\claude\versions\2.1.217"
$s = [Text.Encoding]::GetEncoding('latin1').GetString([IO.File]::ReadAllBytes($p))
[regex]::Matches($s, 'process\.env\.([A-Z][A-Z0-9_]{3,})') |
ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique
v2.1.217 里扒出 522 个。绝大多数是内部开关和 CI 检测用的,别乱设。跟配置真正相关的几类:
路径类跟这次迁移同族。CLAUDE_CODE_PLUGIN_CACHE_DIR 可以单独把插件缓存挪走——我那 8.6 GB 的大头就在这儿,早知道只挪这一个就够了,还不用碰上面第三步。另外有 CLAUDE_TMPDIR 和 CLAUDE_CODE_DEBUG_LOGS_DIR,后者名字里带 DIR 但实际要填文件路径。
超时类常用的是 API_TIMEOUT_MS(默认 10 分钟)和 BASH_DEFAULT_TIMEOUT_MS(默认 2 分钟)。网络不好或者走中转的时候会用上。
关开关的一堆 DISABLE_* 里,CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC 最省事,一把关掉所有非必要请求,比一个个关干净。
除了 CLAUDE_CONFIG_DIR,上面这些更适合写进 settings.json 的 env 块,跟着项目或用户走,不用污染整机环境。CLAUDE_CONFIG_DIR 是唯一的例外,因为它得在读 settings.json 之前就生效,只能走环境变量。