【完全振り返り】pwsh.cmd にやられた Codex Desktop のトラブルシューティング:WSL の誤誘導から Windows Native の真の原因へ(batch file arguments are invalid)

今回のトラブルシュートは、ぜひ丸ごと1本書き残す価値がある。というのも、Windows 上でデスクトップ AI ツールが踏みがちな地雷を、ほぼ全部踏み抜いたからだ:カスタム API、WSL、Windows ネイティブ、PowerShell、PATH、shim、ストア版に内蔵されたバイナリ、そして「突然起きたように見えるが、実際は古い潜在不具合が再トリガーされた」というやつ。

最後にまず結論:

Windows ネイティブ環境で Codex Desktop がコマンドを完全に実行できなくなっていた真因は、WSL でも PowerShell プロファイルでも、デスクトップ版と npm 版のバージョン不一致でもなく、C:\\Users\\1\\AppData\\Local\\Programs\\Python\\Python313\\Scripts\\pwsh.cmd に潜んでいたバッチ shim だった。Codex の Windows コマンド実行器が、この .cmd ラッパーを引数付きで起動しようとした瞬間に batch file arguments are invalid を吐き、すべてのコマンドが実行に入る前に死んでいた。

1. 事故の起点:見た目は「WSL に切り替えたら壊れた」

最初の表面症状には、実は2層あった:

  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 ネイティブの問題を直したい。最初からネイティブが壊れていたから WSL に逃げた」と言っていた。

この一手が重要だった。2つの問題を分離できたからだ:

  • WSL ルートの問題は、ネットワーク/プロキシ/WSL 環境からカスタムエンドポイントにアクセスする系の問題に見える。
  • Windows ネイティブルートの問題は、ローカルのコマンド実行器そのものが既に壊れている。

先に分けないと、以降ずっと2つの独立障害の間で行ったり来たり誤判定し続ける。

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 プロファイル内のどこかのスクリプトが個別コマンドを壊したわけではない。
  • そして cmd.exe /c のような「PowerShell を迂回する」経路でも助からない。

つまり、故障点は「コマンド起動レイヤー」にあり、コマンドそのものではない。

3. 2つ目のミスリード:統合ターミナルは普通に動くのに、なぜ Codex はダメと言うのか?

当時スレッドに貼られた統合ターミナルのスナップショットは正常で、プロンプトも出ていて、次が見えていた:

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

これは非常に人を誤誘導しやすい。「ターミナルが開くなら PowerShell 本体は問題ない」「なら Codex 内蔵 runner の bug だろう」と思いがちになる。

この判断は半分だけ当たり。

PowerShell 本体は確かに壊れていなかったが、Codex Desktop では「ターミナルが表示できる」ことと「agent のコマンド実行器が shell を起動して、引数付きでコマンドを実行できる」ことは別の経路だ。

今回の1つ目の大きな収穫はこれ:

対話型の統合ターミナルが正常でも、agent の shell_command 経路が正常である証明にはならない。

4. 第三ラウンドの誤判:一時はストア版デスクトップが古い backend を内蔵している説を疑った

次に、かなり怪しい事実が見つかった:

  • Microsoft Store 版デスクトップのリソースにバンドルされている 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 の不一致は、せいぜいノイズ、あるいは別途注目すべき課題。
  • しかし今回のネイティブ shell 崩壊の根因ではない。

これが2つ目の大きな収穫:

現象を説明できそうな「怪しい点」はいくらでもあるが、パッチ実験で現象が消えないなら、それは根因ではない。

5. さらに切り分け:PowerShell プロファイルはうるさいが、今回の致命点ではない

途中で PowerShell プロファイルのエラーも見えた。例えば .wt_proxy_config.jsonnull として読み込まれた後、Add-Member$cfg.enabled などのロジックがプロファイル内で爆発していた。

こういうエラーはもちろん直すべきだ。ターミナル起動体験を汚し、shell 初期化が壊れているのではと疑わせる。

ただし、ここにも重要な判断がある:

もし真因がプロファイルなら、少なくとも一部のコマンド経路はそれを回避できるはずだし、エラーの形も PowerShell スクリプト例外寄りになるはずで、全コマンドが起動レイヤーで一律 batch file arguments are invalid にはなりにくい。

後に事実も示した通り、プロファイルは今回のネイティブ executor 完全麻痺の主因ではなかった。

6. 真の突破口:妙な pwsh.cmd を発見

最後に問題を確定させたのは、このファイルの発見だった:

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

中身は2行だけ:

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

これは「親切な shim」に見える。pwsh を1枚包んで、本物の 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. 重要な証拠連鎖:この地雷は今日生成されたものではない

最も面白く、直感に反するのがここ。

ユーザーの体感は「だいたい3時間前に突然発生し、それまでは問題なかった」。

しかしログは、この地雷の歴史が「今日突然」より古いことを示している:

証拠 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. ではなぜユーザーは「3時間前に突然壊れた」と感じたのか?

ここが今回一番きちんと説明する価値がある点。

より正確にはこう言うべきだ:

3時間前に壊れた設定が生成されたのではなく、3時間前ごろに Codex Desktop が再び pwsh.cmd に命中する実行パスを通った。

現時点の証拠から、最も妥当な説明は「ソフトがこっそり更新された」ではなく、次の組み合わせだと思う:

  1. pwsh.cmd という地雷は以前から埋まっていた。
  2. 2月初旬にも同種エラーを引き起こしていたが、常に同じ形で表面化するわけではない。
  3. 今回ユーザーが切り分けのため、Windows ネイティブ + PowerShell 関連の経路へ戻った。
  4. 同時にデスクトップ/バックエンドプロセスが永続環境を取り直し、PATH で pwsh を再解決した。
  5. その解決が .cmd shim に当たり、問題が全面的に再発した。

つまり、この種の障害はソフト更新がなくても「突然起きる」。

以下のどれか1つが起きるだけで、古い潜在不具合が再活性化し得る:

  • デスクトップアプリ再起動
  • バックグラウンド agent プロセス再起動
  • WSL から Windows ネイティブへ切り替え
  • 設定変更後の shell 再初期化
  • 新しいスレッドが古い環境を再利用せず、永続 PATH を読み直す

ここは正直に言っておく必要がある:

「古い潜在不具合 + 新しいトリガー」という構造は確信できるが、現ログだけで“どの瞬間の操作”が引き金だったかを100%断定はできない。

ただし確実なのは、「今日突然生成された未知の新しい壊れ設定」ではない、ということ。

9. 最終的な修復アクション

Windows ネイティブを復活させた手順はシンプルだが、ピンポイントである必要があった:

アクション 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 起動スクリプト側でも PowerShell 7 を prepend

将来どこかが PATH を再び変えても、この起動経路が汚染されにくくする。

修復後、ユーザーは ついに正常復帰した ことを確認済み。

10. 今回どんな回り道をしたのか? なぜその回り道にも価値があったのか?

かなり遠回りに見えるが、各回り道は問題空間を確実に狭めていた:

回り道 1:最初に WSL のネットワークエラーに引っ張られた

意義:WSL の問題とネイティブの問題が同一障害ではないことを確認。

回り道 2:PowerShell プロファイルを疑った

意義:「ターミナル初期化ノイズ」と「コマンド実行器の致命障害」を切り分け。

回り道 3:デスクトップ版の bundled backend が古い説を疑った

意義:patched app 実験で「純粋なバージョン問題」を直接排除。

回り道 4:異なるコマンド入口を試した

例えば直接:

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

意義:どの道も同じレイヤーで事前に落ちることを証明し、特定 shell のバグではないと確定。

つまり、これらの「回り道」は無駄ではなく、「どこでもあり得る」を「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 ネイティブの shell 経路に戻ったタイミングで再び命中し、Codex Desktop のコマンド実行器を丸ごと爆死させた。

今後、次のような症状に再遭遇したら:

  • 全コマンドが一律失敗
  • エラーが短く、コマンド内容に依存しない
  • 統合ターミナルは開くが agent が走らない
  • コマンド入口を変えても全部同じエラー

Git、リポジトリ、プロファイル、さらにはアプリのバージョン自体に固執するのではなく、まずこれを優先して調べるべきだ:

  • where.exe pwsh
  • PATH の順序
  • 同名の .cmd/.bat shim がないか
  • WindowsApps / Python Scripts / カスタム launcher ディレクトリにラッパーが潜んでいないか

この種の問題は、根因さえ見つければ修復アクション自体はだいたい小さい。

本当に難しいのは、根因っぽく見えるノイズの山の中から、真の地雷を力ずくで掘り当てることだ。

TL;DR:今回は WSL、ローカルリポジトリ、PowerShell profile、またはデスクトップ版のバージョン不一致が原因で Codex が壊れたのではなく、真の根本原因は PATH 内で pwshpwsh.exe 本体ではなく、先に .cmd ラッパーにヒットしていたことです。

具体的には、本機にこれがありました:

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

Codex の Windows コマンド実行器が引数付きでこの shim(シム)を呼び出すと、即座にこれが出ます:

batch file arguments are invalid

そのため表面的には「すべてのコマンドが壊れた」ように見えますが、本質的には最外層の shell ブートストラップが、コマンドが本当に実行される前に爆発していました。

修復もシンプルです:

  • この pwsh.cmd を移動/リネームする
  • C:\Program Files\PowerShell\7\ を PATH の先頭側に持ってくる
  • Codex を再起動して新しい環境を反映させる

もう一つ非常に重要な学びは:ユーザーが「今日突然壊れた」と感じても、根本原因が今日になって初めて発生したとは限らないということです。ログを見ると、この shim と同種のエラーは実はもっと早い時点から存在しており、今回はたまたま再起動/パス切り替え後に再びヒットしただけでした。