一次 Codex 调用 mise shim 导致进程递归的排查记录
背景
我在 Windows 上使用 Codex Coding Agent、PowerShell 7 和 mise 管理开发运行时。mise 的配置、缓存和安装目录统一放在 D:\XDG 下:
XDG_CONFIG_HOME = D:\XDG\.config
XDG_DATA_HOME = D:\XDG\.local\share
XDG_CACHE_HOME = D:\XDG\.cache
正常情况下,mise doctor 应该显示:
config: D:\XDG\.config\mise
data: D:\XDG\.local\share\mise
shims: D:\XDG\.local\share\mise\shims
但在 Codex Coding Agent 中,某些命令会调用 D:\XDG\.local\share\mise\shims\node.exe,随后生成大量 node.exe 和 mise.exe 子进程,最终把内存拖死。
现象
在 Codex 环境里运行 node --version 后,进程树中出现了这样的递归链:
node.exe "D:\XDG\.local\share\mise\shims\node.exe" --version
-> mise.exe "mise" x -- node --version
-> node.exe "D:\XDG\.local\share\mise\shims\node.exe" --version
-> mise.exe "mise" x -- node --version
...
这个链会持续扩张。一次复现中,短时间内生成了数百个 node.exe / mise.exe 进程。
为了止血,我先临时禁用了 Node shim:
Rename-Item `
'D:\XDG\.local\share\mise\shims\node.exe' `
'node.exe.disabled-by-codex-rootcause'
禁用 shim 后,再清理残留 node.exe / mise.exe 进程,递归才停止。
初步误判
一开始我怀疑是 Codex 从旧的 GUI 父进程继承了过期环境变量。因为 Codex 的父进程链是:
Microsoft.CmdPal.UI.exe
-> Codex.exe
-> codex.exe app-server
-> pwsh.exe
Windows 进程的环境变量在进程创建时固定,后续修改系统环境变量不会自动同步到已运行的 GUI 进程。这个判断有一定合理性。
但注销、重启过,问题依然存在。因此根因不应继续归因于“旧进程没有刷新环境”。
关键差异
在 Windows Terminal 的 PowerShell 中,mise doctor 正常:
shims_on_path: yes
dirs:
cache: D:\XDG\.cache\mise
config: D:\XDG\.config\mise
data: D:\XDG\.local\share\mise
shims: D:\XDG\.local\share\mise\shims
config_files:
D:\XDG\.config\mise\config.toml
toolset:
core:node@26.4.0
core:python@3.14.6
aqua:pnpm/pnpm@11.10.0
...
但在 Codex 的 shell command 环境中,最初看到的是:
XDG_CONFIG_HOME = empty
XDG_DATA_HOME = empty
XDG_CACHE_HOME = empty
对应的 mise doctor 退回默认目录:
dirs:
cache: ~\AppData\Local\Temp\mise
config: ~\.config\mise
data: ~\AppData\Local\mise
shims: ~\AppData\Local\mise\shims
config_files:
(none)
toolset:
(empty)
但是 Codex 的 PATH 里仍然有:
D:\XDG\.local\share\mise\shims
这就形成了危险组合:
PATH 上有 D:\XDG 的 mise shims
但当前进程没有 XDG_* 环境变量
mise 自己退回 AppData 默认目录
toolset 为空
真实 install dirs 没有被正确插到 shim 前面
真正根因
进一步验证发现,注册表里的环境变量是存在的:
[Environment]::GetEnvironmentVariable('XDG_CONFIG_HOME', 'User')
[Environment]::GetEnvironmentVariable('XDG_CONFIG_HOME', 'Machine')
都能读到:
D:\XDG\.config
XDG_DATA_HOME 和 XDG_CACHE_HOME 也一样存在。
但是当前 Codex 启动的命令进程里,Process 作用域没有这些变量:
Process XDG_CONFIG_HOME = empty
Process XDG_DATA_HOME = empty
Process XDG_CACHE_HOME = empty
同一个 Codex 命令里,手动把注册表中的值注入当前进程:
$env:XDG_CONFIG_HOME = [Environment]::GetEnvironmentVariable('XDG_CONFIG_HOME', 'User')
$env:XDG_DATA_HOME = [Environment]::GetEnvironmentVariable('XDG_DATA_HOME', 'User')
$env:XDG_CACHE_HOME = [Environment]::GetEnvironmentVariable('XDG_CACHE_HOME', 'User')
mise doctor
mise doctor 立刻恢复正常,重新指向 D:\XDG。
因此根因不是 mise 配置错误,也不是 Windows 环境变量没有设置,而是:
Codex 的命令执行环境不是完整继承 Windows 登录环境,而是由 Codex/app-server 构造过的进程环境块。它保留或重建了
PATH和一部分常见变量,但漏掉了自定义的XDG_*环境变量。
同时也观察到 Codex 进程环境中缺少一些常见 Windows 变量,例如:
OS
NUMBER_OF_PROCESSORS
PROCESSOR_ARCHITECTURE
windir
这说明问题不是只针对 XDG_*,而是 runner 环境构造策略本身不是完整继承。
为什么会导致 shim 递归
正常的 mise 激活后,PATH 顺序应该类似:
D:\XDG\.local\share\mise\installs\node\26.4.0
D:\XDG\.local\share\mise\shims
也就是说,node 会先命中真实 Node:
D:\XDG\.local\share\mise\installs\node\26.4.0\node.exe
而不是 shim。
出问题时,Codex 环境里没有 XDG_*,mise 不知道应该使用 D:\XDG 那套配置和安装目录,于是没有把真实 install dirs 正确放到 shim 前面。但 PATH 上仍有:
D:\XDG\.local\share\mise\shims
因此 node 命中的是 shim:
D:\XDG\.local\share\mise\shims\node.exe
shim 又调用:
mise x -- node --version
而 mise x 内部再次解析 node 时,仍然从 PATH 命中同一个 shim。于是形成:
node shim -> mise x -> node shim -> mise x -> ...
这就是进程爆炸的直接原因。
修复方案
最终采用的修复是:在 PowerShell profile 中,在 mise activate 之前,从 User/Machine 作用域补回当前进程缺失的 XDG 变量。
放在这一行之前:
(&mise activate pwsh) | Out-String | Invoke-Expression
补丁如下:
foreach ($name in @('XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_CACHE_HOME')) {
if (-not [Environment]::GetEnvironmentVariable($name, 'Process')) {
$value = [Environment]::GetEnvironmentVariable($name, 'User')
if (-not $value) {
$value = [Environment]::GetEnvironmentVariable($name, 'Machine')
}
if ($value) {
[Environment]::SetEnvironmentVariable($name, $value, 'Process')
}
}
}
这样即使 Codex 启动 shell 时没有传入 XDG_*,PowerShell profile 也会在 mise 初始化前补齐。
修复后,Codex 中重新验证:
XDG_CONFIG_HOME = D:\XDG\.config
XDG_DATA_HOME = D:\XDG\.local\share
XDG_CACHE_HOME = D:\XDG\.cache
mise doctor 恢复为:
shims_on_path: yes
dirs:
cache: D:\XDG\.cache\mise
config: D:\XDG\.config\mise
data: D:\XDG\.local\share\mise
shims: D:\XDG\.local\share\mise\shims
config_files:
D:\XDG\.config\mise\config.toml
toolset:
core:node@26.4.0
core:python@3.14.6
...
并且 Get-Command node 解析到真实安装目录:
D:\XDG\.local\share\mise\installs\node\26.4.0\node.exe
这说明递归条件已经消失。
恢复 shim
在确认 Codex 里的 mise doctor 已经指向 D:\XDG 后,可以恢复之前临时禁用的 Node shim:
Rename-Item `
'D:\XDG\.local\share\mise\shims\node.exe.disabled-by-codex-rootcause' `
'node.exe'
mise reshim
然后验证:
mise doctor
Get-Command node -All
node --version
期望结果:
mise doctor 不再报告 Missing shims: node.exe
node --version 输出 v26.4.0
没有新的 node.exe / mise.exe 递归进程
定位思路总结
这类问题不要只看“在我自己的终端里是否正常”,而要看失败进程实际看到的环境。
排查顺序可以是:
- 看命令解析结果:
Get-Command node -All
Get-Command mise -All
- 看当前进程环境:
$env:XDG_CONFIG_HOME
$env:XDG_DATA_HOME
$env:XDG_CACHE_HOME
$env:PATH
- 看注册表环境:
[Environment]::GetEnvironmentVariable('XDG_CONFIG_HOME', 'User')
[Environment]::GetEnvironmentVariable('XDG_CONFIG_HOME', 'Machine')
- 看 mise 实际使用的目录:
mise doctor
- 看是否存在递归进程链:
Get-CimInstance Win32_Process |
Where-Object { $_.Name -match 'mise|node' } |
Select-Object ProcessId, ParentProcessId, Name, CommandLine
如果看到:
node shim -> mise x -- node -> node shim
就说明已经不是普通 PATH 错误,而是 shim 递归。
预防建议
1. 不要只依赖 GUI 应用完整继承环境变量
GUI 应用、后台服务、Electron 应用、Agent runner 都可能重建环境变量。只要依赖自定义环境变量,就应该在 shell 初始化阶段做兜底。
对 mise 这种强依赖 XDG_* 的配置,建议在 profile 中显式补齐关键变量。
2. mise activate 必须发生在 XDG 变量之后
顺序很重要:
# 先补 XDG
# 再 mise activate
(&mise activate pwsh) | Out-String | Invoke-Expression
如果先 activate,mise 已经按错误目录完成初始化,后面再补变量也来不及。
3. shims 不应优先于真实 install dirs
正常激活后,真实 install dirs 应该排在 shim 前面。可用下面的方式检查:
($env:PATH -split ';') | Select-String 'mise'
Get-Command node -All
理想结果是 node 首先解析到:
D:\XDG\.local\share\mise\installs\node\...\node.exe
而不是:
D:\XDG\.local\share\mise\shims\node.exe
4. 对 Agent 环境做启动自检
可以在 Agent 工作流开始时检查:
if (-not $env:XDG_CONFIG_HOME -or -not $env:XDG_DATA_HOME -or -not $env:XDG_CACHE_HOME) {
throw "XDG environment variables are missing in this process."
}
mise doctor
如果 mise doctor 显示 ~\AppData\Local\mise,就不要继续运行 node、python、pnpm 这类 shim 命令。
5. 出现递归时优先止血
如果已经发生进程爆炸,先阻断递归入口,再清进程。
对这次问题,阻断入口是临时禁用 node.exe shim:
Rename-Item `
'D:\XDG\.local\share\mise\shims\node.exe' `
'node.exe.disabled-by-codex-rootcause'
然后再清理残留进程。否则单纯杀进程可能追不上新的子进程派生。
结论
这次问题的表面现象是 Codex 调用 mise shim 导致 node.exe / mise.exe 无限递归;直接原因是 node 解析到了 shim,而 shim 内部再次通过 PATH 解析到自己。
更深层的根因是 Codex 的 tool runner 没有把 Windows 注册表中的自定义 XDG_* 环境变量带入命令进程,导致 mise 在 Codex 环境中退回默认 AppData 目录,而 PATH 上又保留了 D:\XDG 的 shims。
最终修复不是改 mise,也不是反复重启 Codex,而是在 PowerShell profile 中,在 mise activate 之前补齐 XDG_CONFIG_HOME、XDG_DATA_HOME、XDG_CACHE_HOME。这样可以让 Codex、普通终端和其它 PowerShell 启动场景都稳定使用同一套 mise 目录。
更多推荐
所有评论(0)