Codex 対 PowerShell:powershell-safe-invocation Skill は何を解決するのか

PowerShell で Codex がスクリプトを何度も修正しているスクリーンショットをきっかけに、Windows で Agent が PowerShell の呼び出し、ファイル書き込み、引数渡しを安全に行うための実用的なルールを整理します。

最近、とてもネタとして強いスクリーンショットを見かけました。Codex が PowerShell で Markdown ファイルを修正していて、最初は here-string が実行されず、次に CRLF/LF の一致に失敗し、さらに Markdown のバッククォートが PowerShell に解釈され、パスのバックスラッシュ処理が不安定になり、-split の引数構文でもつまずく。モデルは自己診断しながらスクリプトを直し続け、最後にコメント欄で「クラシック GPT 5.5 対 PowerShell」という、かなり的確なタイトルを付けられていました。

PowerShell で Codex がスクリプトを何度も修正しているスクリーンショット

この連続修正は笑えますが、背景にある落とし穴は珍しくありません。

この画像が面白いのは、現実の開発現場にかなり近いからです。PowerShell は単なる「Windows 版 Bash」ではありません。独自の文字列ルール、パラメータバインディング、cmdlet のエラーモデル、ネイティブコマンドへの引数渡しがあります。Agent が Bash の習慣を PowerShell に持ち込むと、ファイル書き込み、テキスト置換、外部プログラムの呼び出しといった普通の作業が、細かい失敗の連鎖になります。

Misaka-Mikoto-Tech の powershell-safe-invocation skill は、こうした落とし穴を簡潔なルールにまとめています。これは「PowerShell 構文をもっと覚える」ためのものではありません。Windows でコマンドを実行するとき、Agent が失敗しにくい呼び出し形式を先に選べるようにするためのものです。

最大の問題は PowerShell が難しいことではなく、Agent が Bash として扱ってしまうこと

スクリーンショットに出ている問題はいくつも典型的です。

一つ目は文字列と改行です。Markdown にはバッククォートがあり、PowerShell でもバッククォートはエスケープ文字です。文書は CRLF なのに、スクリプトは LF で厳密に一致させようとすることがあります。here-string の開始や終了の書式も、インデントや引用符が少し違うだけで、意図したテキストを生成しないことがあります。

二つ目はパスと引数です。Windows のパスにはバックスラッシュが含まれ、ディレクトリ名には空白、角括弧、日本語や中国語などが入ることもあります。コマンドを巨大な 1 本の文字列として組み立ててから実行すると、ある引数が意図せず分割されたり、パスの一部がエスケープ、ワイルドカード、正規表現、または別の shell の構文として扱われたりします。

三つ目はエラー判定です。外部プログラムは $LASTEXITCODE を見るべきですが、PowerShell cmdlet は終了エラーと $ErrorActionPreference = 'Stop' に頼るべきです。Agent がこの二つのモデルを混ぜると、「実際には成功していないのに、スクリプトは成功したと思っている」状態になります。

これらは単体では大事故ではありませんが、注意力を削ります。Agent は「コマンドは実行されたように見える」ことを根拠に、間違った前提のまま修正を続けてしまい、だんだん複雑になります。

この skill の核心:巨大な文字列を組み立てない

powershell-safe-invocation で最も重要なルールは、実行ファイル、パス、引数、引用符を 1 つの巨大なコマンド文字列に連結しないことです。

ネイティブプログラムを呼び出すときは、各引数を配列の独立した要素として扱います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$exe = 'C:\Path With Spaces\tool.exe'
$args = @(
    '--input'
    'C:\Data Folder\input.json'
    '--flag'
)

& $exe @args

$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) {
    throw "$exe failed with exit code $exitCode"
}

これは 1 行のコマンドより冗長に見えますが、リスクを分離しています。パスはパス、引数は引数、呼び出しは呼び出しです。Agent は、その引用符が PowerShell 用なのか、cmd 用なのか、対象プログラム用なのかを推測する必要がなくなります。

PowerShell cmdlet には、hashtable splatting のほうが向いています。

1
2
3
4
5
6
7
8
$params = @{
    LiteralPath = 'C:\Data[1]\input.txt'
    Destination = 'C:\Output'
    Force       = $true
    ErrorAction = 'Stop'
}

Copy-Item @params

ここで重要なのは -LiteralPath です。実際のファイルパスは、基本的にはリテラルとして扱うべきです。ワイルドカード展開が本当に必要なときだけ、展開されるパス引数を使います。Agent にとっては、パスに [, ], *, ? が含まれるかをその場で判断するより安定します。

複雑なコマンドを -Command に詰め込まない

Windows 自動化のトラブルは、よく次のようなコマンドから始まります。

1
cmd.exe /c pwsh.exe -Command "..."

この中に JSON、XML、正規表現、パイプライン、リダイレクト、複数行テキスト、非 ASCII パス、Markdown のバッククォートが入ると、エスケープの層がすぐに制御しにくくなります。人間にも読みにくく、Agent にはさらに誤判定しやすい形です。

skill の提案は素朴です。少しでも複雑なら一時的な .ps1 ファイルを書き、pwsh.exe -NoLogo -NoProfile -NonInteractive -File script.ps1 で実行します。

これは Agent に特に向いています。Agent はスクリプトファイルを生成したあと、内容、実行結果、ファイル差分を段階的に確認できます。すべてを 1 つの -Command に押し込むと、失敗時には引用符とエスケープの塊だけが残ります。

powershell.exepwsh.exe ではない

もう一つ見落としやすい点はバージョンです。

PowerShell 7 をインストールしても、powershell.exe が自動的に PowerShell 7 になるわけではありません。通常は次の通りです。

  • powershell.exe は Windows PowerShell 5.1
  • pwsh.exe は PowerShell 7

両者はネイティブコマンドへの引数渡し、既定のエンコーディング、細かい挙動が完全には同じではありません。Agent がどの shell で実行されているかわからないなら、先に確認すべきです。

1
2
$PSVersionTable.PSVersion
$PSNativeCommandArgumentPassing

これは儀式ではありません。「ローカルでは動くのに Agent では失敗する」問題の多くは、shell のバージョンや引数渡し規則の違いが原因です。

ファイル書き込みでは、エンコーディングと改行を第一級の問題として扱う

スクリーンショットで最も興味深いのは、Agent が Markdown ファイルの置換失敗をめぐって何度も回り道している点です。ここには少なくとも三つの前提が隠れています。

  • ファイルの実際のエンコーディング
  • 改行が CRLF か LF か
  • 置換対象が PowerShell の文字列構文で変化していないか

中国語や日本語の本文、Markdown のコードブロック、front matter、shortcode、バッククォートが混在する場合、思いつきの PowerShell here-string で本文を書き込むのは避けたほうが安全です。明示的なエンコーディングのファイル API を使うか、スクリプト側では ASCII の制御ロジックだけを扱い、非 ASCII の本文はより信頼できる経路で書き込むべきです。

Agent ワークフローでは、書き込み後すぐに検証する習慣も重要です。少なくとも UTF-8 として読めるか、front matter が閉じているか、Markdown リンクが壊れていないか、本文に連続した疑問符や明らかな mojibake がないかを確認します。文字化けは早く見つけるほど安く済みます。

より安定した判断順序

この skill で最も実用的なのは、Agent に判断順序を与えている点です。

  1. PowerShell cmdlet で済むなら cmdlet を使う。
  2. 外部プログラムを呼び出すときは & $exe @args を使う。
  3. コマンドが複雑なら .ps1 ファイルを書き、pwsh.exe -File で実行する。
  4. プロセス引数を厳密に制御する必要があるなら ProcessStartInfo.ArgumentList を使う。
  5. 新しいウィンドウ、昇格、デタッチ実行などの特殊な動作が必要なときだけ Start-Process を使う。
  6. cmd の意味論が本当に必要なときだけ cmd.exe /c を使う。
  7. Invoke-Expression は最後の手段として、非常に限定的に使う。

この順序によって、「動く」だけでなく「説明できる、確認できる、再現できる」状態になります。人間の開発者にも大事ですが、Agent にはさらに重要です。

Codex への現実的な助言

Codex に Windows リポジトリ内のファイルを編集させるなら、私はいくつかの強い制約を与えます。

  • PowerShell で Bash 風の \" エスケープを使わない。
  • パスと引数を 1 本の文字列に連結しない。
  • 複雑な引数に Start-Process -ArgumentList を使わない。
  • 普通の呼び出しを解決するために cmd.exe /c をかぶせない。
  • 実在するファイルパスには -LiteralPath を優先する。
  • 複雑なスクリプトは .ps1 に書き、pwsh.exe -File で実行する。
  • 中国語、日本語などの非 ASCII 内容を書き込んだら、必ずエンコーディングと文字化けの兆候を確認する。

PowerShell は Agent の敵ではありません。本当の問題は、Agent が「文字列を連結して shell に推測させる」という古い癖に寄りがちなことです。呼び出しの境界を明確にすれば、PowerShell はむしろ強い構造化能力を提供できます。

つまり、この「Codex 対 PowerShell」の結論は、Agent が Windows を避けるべきということではありません。必要なのは戦術マニュアルです。引用符に賭ける量を減らし、構造を増やす。1 行コマンドに詰め込む量を減らし、確認できる中間状態を残す。

笑いどころはスクリーンショットにあり、教訓はルールにあります。

记录并分享
Hugo で構築されています。
テーマ StackJimmy によって設計されています。