[完整复盘] 一次被 pwsh.cmd 阴掉的 Codex Desktop 故障排查:从 WSL 误导到 Windows Native 真根因(batch file arguments are invalid)

这次排错非常值得完整记一篇,因为它几乎把桌面 AI 工具在 Windows 上最容易踩的几类坑都踩了一遍:自定义 API、WSL、Windows native、PowerShell、PATH、shim、商店版内置二进制、以及“看起来像突然出现,实际上是旧隐患被重新触发”。

最后先说结论:

真正导致 Codex Desktop 在 Windows native 下彻底跑不了命令的,不是 WSL,不是 PowerShell profile,也不是桌面版和 npm 版版本不一致,而是一个藏在 C:\Users\1\AppData\Local\Programs\Python\Python313\Scripts\pwsh.cmd 的批处理 shim。Codex 的 Windows 命令执行器在带参数启动这个 .cmd 包装器时直接报:batch file arguments are invalid,于是所有命令在真正执行前就死掉了。

1. 事故起点:看起来像是“切到 WSL 后坏了”

最开始表面现象其实有两层:

  1. 把 Codex 设置里的 Agent 和 Integrated terminal 都切到 WSL 后,桌面端开始不停重连,报错:
Reconnecting... 1/5
...
stream disconnected before completion: error sending request for url (https://wuju.de5.net/v1/responses)
  1. 但用户明确说,先别修 WSL,要先把原来的 Windows native 问题搞定,因为一开始就是 native 有问题,才切去 WSL 的。

这一步很重要,因为它把两个问题拆开了:

  • WSL 路线的问题更像是网络/代理/WSL 环境访问自定义接口的问题。
  • Windows native 路线的问题则是本地命令执行器本身已经坏了。

如果不先拆开,后面会一直在两个独立故障之间来回误判。

2. 第一轮最小复现:不是某条命令坏了,是“所有命令在执行前就死了”

为了确认到底是 Git、PowerShell 还是 Codex 本身的问题,我们做了最小命令测试:

  • Write-Output hi
  • Get-Location
  • git status -sb
  • cmd.exe /c echo hi

结果非常整齐,全部统一报错:

execution error: Io(Error { kind: InvalidInput, message: "batch file arguments are invalid" })

这里的价值非常大,因为它直接说明:

  • 不是 Git 特有的问题。
  • 不是某个工作目录的问题。
  • 不是 PowerShell profile 里某段脚本把具体命令搞坏了。
  • 甚至不是 cmd.exe /c 这种“绕过 PowerShell”的路径能幸免。

也就是说,故障点在“命令启动层”,而不是命令本身。

3. 第二个误导点:集成终端明明正常,为什么 Codex 说不行?

当时线程里的集成终端快照是正常的,提示符能出来,能看到:

PowerShell 7.5.4
PS C:\Users\1\0.0.90_0>

这很容易把人带偏,以为:

  • 既然终端能打开,PowerShell 本体应该没问题。
  • 那剩下多半是 Codex 内置 runner 的 bug。

这判断只对了一半。

PowerShell 本体确实没坏,但 Codex Desktop 里“终端能显示”不等于“agent 的命令执行器能成功启动 shell 并带参数跑命令”。这两个链路不是一回事。

这是这次排错的第一个大收获:

交互式集成终端正常,并不能证明 agent 的 shell_command 链路正常。

4. 第三轮误判:一度怀疑是商店版桌面内置了旧 backend

接着我们发现一件非常可疑的事:

  • 微软商店版桌面资源里 bundled 的 CLI/runner 版本是 0.112.0-alpha.3
  • 本机 npm 全局装的 @openai/codex 已经是 0.113.0

这看起来特别像根因,因为“桌面壳子新,内置运行时旧”确实很可能导致一堆离谱行为。

于是我们做了一个相当硬核但很有价值的验证:

  • 从商店版复制出一份可写的桌面程序副本。
  • 用 npm 安装的 0.113.0 二进制替换其中的 codex.execodex-command-runner.exe 等组件。
  • 启动 patched 版桌面,再在新线程里复现。

如果真是版本不一致导致的,那么 patched 版应该恢复。

结果是:完全没有恢复。

新线程里最简单的 git status -sb 依然原样报:

batch file arguments are invalid

这一步非常关键,因为它把一个非常像根因的猜测直接排除了。

结论变成:

  • 0.112.0-alpha.30.113.0 的不一致,最多只是噪音,或者是另外一个值得关注的问题。
  • 但它不是这次 native shell 崩溃的根因。

这也是第二个大收获:

能解释现象的“可疑点”很多,但只要补丁实验无法消除现象,它就不是根因。

5. 继续排除:PowerShell profile 很吵,但不是这次致命点

中途还看到了 PowerShell profile 报错,例如 .wt_proxy_config.json 读取为 null 后,Add-Member/$cfg.enabled 之类逻辑在 profile 里炸掉。

这类报错当然要修,因为会污染终端启动体验,也会让人怀疑 shell 初始化流程有问题。

但这里也有一个关键判断:

如果真正根因是 profile,至少有些命令路径应该能绕开它,或者错误形态应该偏向 PowerShell 脚本异常,而不是所有命令统一在启动层直接报 batch file arguments are invalid

后来事实也证明,profile 不是这次 native executor 彻底瘫痪的主因。

6. 真正突破口:发现了一个很奇怪的 pwsh.cmd

最后真正把问题钉死的,是在本机发现了这个文件:

C:\Users\1\AppData\Local\Programs\Python\Python313\Scripts\pwsh.cmd

内容只有两行:

@echo off
"C:\Program Files\PowerShell\7\pwsh.exe" %*

这看起来像是一个“好心的 shim”,目的是把 pwsh 包一层,转发到真正的 PowerShell 7。

但对 Codex Desktop 的 Windows 执行器来说,这玩意是毒药:

  • 它不是 pwsh.exe,而是一个 .cmd 批处理包装器。
  • Codex 的命令执行器在某条链路上带参数调用到它时,会直接触发:
batch file arguments are invalid

换句话说,命令还没到 pwsh.exe,就已经死在 .cmd 这一层了。

这也完美解释了为什么:

  • Write-Output hi 会死
  • Get-Location 会死
  • git status -sb 会死
  • cmd.exe /c echo hi 也会死

因为挂掉的是最外层 shell bootstrap,不是目标命令。

7. 关键证据链:这个坑不是今天才生成的

最有意思也最反直觉的地方在这里。

用户感知是:“大概三小时前突然出现,此前没有问题。”

但日志说明,这个坑的历史比“今天突然出现”更早:

证据 1:pwsh.cmd shim 在 2026-02-02 18:52:53(本地时间)就被写入了

codex-tui.log 里能看到当时有过明确的写入动作,直接往 Python 的 Scripts 目录里写了 pwsh.cmd

证据 2:同一类错误最早在 2026-02-03 15:02:40(本地时间)就已经出现过

日志里从 2 月 3 日开始,已经出现了大量:

exec error: batch file arguments are invalid

甚至还有显式尝试调用这个 shim 的记录。

证据 3:本轮故障是在 2026-03-11 04:11 左右重新暴露,04:33 的复现实验稳定命中

也就是说,今天并不是“这个坏配置今天才被创建”,而是“这个旧隐患今天又一次被触发,并且正好打到了当前使用路径”。

8. 那为什么用户会感觉它是“三小时前突然坏的”?

这是这次最值得讲清楚的一点。

更准确的说法应该是:

不是三小时前才生成了坏配置,而是三小时前左右 Codex Desktop 再次走到了那条会命中 pwsh.cmd 的执行路径。

根据现有证据,我认为最合理的解释是下面这个组合,而不是“软件刚刚偷偷更新”:

  1. pwsh.cmd 这个地雷早就埋下了。
  2. 它在 2 月初就已经造成过同类错误,但不是每次使用都会以同样方式暴露。
  3. 这次用户为了排查,又切到了 Windows native + PowerShell 相关路径。
  4. 同时桌面端/后端进程重新取了一次持久环境,重新按 PATH 解析 pwsh
  5. 这次解析命中了那个 .cmd shim,于是问题重新全面爆发。

换句话说,这种故障不需要软件更新也能“突然出现”。

只要下面任意一件事发生,就可能把旧隐患重新激活:

  • 桌面应用重启
  • 后台 agent 进程重启
  • 从 WSL 切回 Windows native
  • 改动设置后重新初始化 shell
  • 某次新线程不再复用旧环境,而是重新读取持久 PATH

这里要诚实一点:

我能确认“旧隐患 + 新触发”这个结构,但不能仅凭现有日志 100% 证明是哪一个瞬间动作触发了它。

不过可以非常确定:不是“今天才突然生成的未知新坏配置”。

9. 最终修复动作

真正让 Windows native 恢复的动作很简单,但必须精准:

动作 1:把那个 shim 挪走

把:

C:\Users\1\AppData\Local\Programs\Python\Python313\Scripts\pwsh.cmd

重命名为:

C:\Users\1\AppData\Local\Programs\Python\Python313\Scripts\pwsh.cmd.disabled-by-codex

动作 2:把 PowerShell 7 提前到用户 PATH 前面

显式把:

C:\Program Files\PowerShell\7\

放到用户 Path 的前面,避免后续某些解析路径又先撞到别的 shim。

动作 3:广播环境变量变更

不是只改注册表,还显式发了 WM_SETTINGCHANGE / Environment,让新进程尽快拿到更新后的环境。

动作 4:patched 启动脚本也主动 prepend PowerShell 7

这样即便以后别的地方又改 PATH,这个启动链路也更不容易被污染。

修完后,用户已经确认:终于恢复正常。

10. 这次到底走了哪些弯路?为什么这些弯路仍然值得?

看起来绕了很多,但每个弯路其实都在收缩问题空间:

弯路 1:先被 WSL 网络错误带偏

意义:确认 WSL 问题和 native 问题不是一个故障。

弯路 2:怀疑 PowerShell profile

意义:把“终端初始化噪音”与“命令执行器致命故障”区分开。

弯路 3:怀疑桌面版 bundled backend 版本落后

意义:通过 patched app 实验,直接排除“纯版本问题”。

弯路 4:尝试不同命令入口

比如直接跑:

  • Write-Output hi
  • cmd.exe /c echo hi
  • powershell -NoProfile -Command ...
  • & git.exe status -sb

意义:证明所有路都在同一层前置失败,而不是某个 shell 特有 bug。

所以这些“弯路”并不是白走,而是一步步把问题从“可能是任何地方”压缩成“只能是 very low-level 的 Windows shell bootstrap / PATH / shim 问题”。

11. 这次最实用的经验总结

经验 1:桌面 AI 工具里,“终端显示正常”不代表 agent 执行器正常

UI 终端和后台命令执行链路是两回事。

经验 2:最短命令测试非常值钱

Write-Output hiGet-Locationcmd.exe /c echo hi 这种测试比上来就跑复杂命令有效得多。

经验 3:版本差异不等于根因

如果替换了二进制版本,现象完全不变,那版本问题最多只是伴随现象。

经验 4:Windows 上的 shim 非常阴

尤其是 .cmd.bat、Python Scripts 目录、WindowsApps 代理 stub 这些东西,平时看着没事,一旦某个程序用的不是你想象中的解析方式,就会炸得特别离谱。

经验 5:用户“感觉今天突然坏了”不一定等于“根因今天才出现”

很多系统级问题的真实结构是:

  • 隐患很早就存在
  • 中间长期没有命中
  • 某次进程重启/模式切换/环境重读后,突然全面爆发

经验 6:日志时间线极其重要

如果只看当下,很容易把“今天触发”误判成“今天创建”。

12. 这次最本质的一句话结论

这不是一次简单的“Codex 坏了”,而是一次典型的 Windows 环境层污染问题:一个早就埋下的 pwsh.cmd shim,在某次重新走 Windows native shell 路径时被重新命中,最终把 Codex Desktop 的命令执行器整体炸死。

如果以后再遇到类似的:

  • 所有命令统一失败
  • 错误都很短且和命令内容无关
  • 集成终端能开但 agent 不能跑
  • 不同命令入口全都同样报错

那就别再盯着 Git、仓库、profile、甚至应用版本本身了,优先去查:

  • where.exe pwsh
  • PATH 顺序
  • 有没有同名 .cmd/.bat shim
  • WindowsApps / Python Scripts / 自定义 launcher 目录里是否藏了包装器

这类问题,一旦发现根因,修复动作反而通常很小。
真正难的是:在一堆特别像根因的噪音里,硬生生把那颗真正的地雷找出来。

TL;DR:这次不是 WSL、本地仓库、PowerShell profile 或桌面版版本不一致把 Codex 搞坏了,真正根因是 pwsh 在 PATH 里先命中了一个 .cmd 包装器,而不是 pwsh.exe 本体。

具体来说,本机有个:

C:\Users\1\AppData\Local\Programs\Python\Python313\Scripts\pwsh.cmd

Codex 的 Windows 命令执行器带参数调用到这个 shim 时,直接报:

batch file arguments are invalid

所以表面看是“所有命令都坏了”,本质上其实是最外层 shell bootstrap 在命令真正执行前就炸了。

修复也很直接:

  • 把这个 pwsh.cmd 挪走/改名
  • C:\Program Files\PowerShell\7\ 提前到 PATH 前面
  • 重启 Codex 让新环境生效

另外一个很关键的经验是:用户感觉它是“今天突然坏了”,不等于根因是今天才出现。日志能看到这个 shim 和同类报错其实更早就存在了,这次只是某次重启/切换路径后重新命中而已。