Claude CodeでPyInstallerのexe化を任せる — 実行して確かめる手順
Claude CodeにPythonのexe化を任せるとき、ビルド成功で終わらせず、dist内の実行ファイルを起動して確かめさせる手順と、dist・buildを手で直させない設定をまとめます。
PyInstallerは、Pythonスクリプトを実行ファイルにまとめるツールです。Claude Codeに「exe化して」と頼むと、pyinstaller が終わった時点で「完了しました」と返ってくることがあります。ところがビルドが通っても、生成物が起動するとは限りません。ここでは、ビルド後に dist の実行ファイルを実際に動かして確認させる流れと、dist・build を手で直させないための設定を扱います。
ビルドが通っても生成物が動くとは限らない
PyInstallerは、スクリプトを解析してモジュールの依存を集め、dist に実行ファイルをまとめます。解析はスクリプトのコードから見える範囲で行うため、コードの外から読み込まれる物は漏れる余地があります。
漏れが出やすいのは次の3つです。
- 文字列でモジュール名を渡して動的に読み込むimport
- スクリプトと同じ場所に置いた設定ファイルや画像
- パッケージが内部で使う共有ライブラリ
オプション一覧には、それぞれに対応する指定があります。--hidden-import はコードから見えないimportを名指しし、--add-data はデータファイルを同梱します。--collect-all はパッケージのサブモジュール、データ、バイナリをまとめて集めます。
これらは足りなかった時点でビルドが失敗するのではなく、起動したときに初めて見つからないというエラーになるのが厄介です。したがって完了の条件は「pyinstallerが終了コード0で終わった」ではなく、「生成した実行ファイルが起動して期待どおり終了した」に置く必要があります。
exe化が終わったと言える条件
- Qpyinstallerは最後までエラーなく終わったか?答えがいいえのとき答えがはい
ビルドログの最後のエラーから直します。
- Qdist内の実行ファイルを起動して、終了コード0で終わったか?答えがいいえのとき答えがはい
起動時のエラー文言から、足りないimportやデータを特定します。
ここで初めて完了
別のフォルダーから起動しても同じ結果になることまで見ておくと、相対パスの取りこぼしも拾えます。
起動確認を最初から組み込んだ題材
確認を自動で回すには、実行ファイルが「起動して、必要なものを読み込み、終了する」モードを持っていると楽です。次の最小例は、設定ファイルを読んで1行出力し、--self-test が付いたときは終了コード0で終わります。
# app.py
import sys
from pathlib import Path
def main() -> int:
config = Path(__file__).with_name("config.txt").read_text(encoding="utf-8")
print(f"config: {config.strip()}")
if "--self-test" in sys.argv:
return 0
input("Enterで終了")
return 0
if __name__ == "__main__":
sys.exit(main())--self-test を用意するのは、確認のたびに入力待ちで固まるのを避けるためです。GUIアプリやサーバーを作る場合も、起動して初期化まで済ませたら終わるモードを1つ持たせると、Claude Codeが自分で実行して結果を読めます。
ビルドと起動確認をClaude Codeに回させる
使うオプションは、PyInstallerのオプション一覧にあるものだけです。-F(--onefile)は1ファイルにまとめ、-y(--noconfirm)は出力先を確認なしで置き換えます。--clean はキャッシュと作業ファイルを消してからビルドします。
pyinstaller --onefile --clean --noconfirm \
--add-data "config.txt:." \
app.py
./dist/app --self-test
echo "exit code: $?"--add-data の書式は SOURCE:DEST で、コピー先をトップレベルに置くときは . を指定します。Windowsでは実行ファイルが dist\app.exe になるため、起動の行もそれに合わせます。
この流れをClaude Codeへ渡すときは、依頼文に完了条件を入れます。次のような書き方が出発点になります。
app.py をPyInstallerで1ファイルにしてください。
完了の条件は、dist/app を --self-test 付きで実行して終了コード0になることです。
起動に失敗したら、エラーに出たモジュール名やファイル名から原因を特定し、
.specまたはコマンドのオプションを直して、ビルドからやり直してください。ポイントは、直す場所を「ビルドの指定」に限っていることです。dist の中身を直しても次のビルドで置き換わるだけなので、直す対象が違います。
起動に失敗したときの切り分け
実行ファイルが起動で落ちたら、手がかりはエラーメッセージに含まれるモジュール名やファイル名です。Claude Codeには、出力を読んで次の手を選ばせます。
| 起動時の症状 | 疑うこと | 試す指定 |
|---|---|---|
| モジュールが見つからない | 疑うことコードから見えないimport | 試す指定--hidden-import |
| ファイルが見つからない | 疑うことデータの同梱漏れ | 試す指定--add-data |
| パッケージ内部の資源が足りない | 疑うことデータ・バイナリの収集漏れ | 試す指定--collect-all |
| 何が読み込まれているか不明 | 疑うこと初期化の経路を知りたい | 試す指定--debug=imports |
--debug は all、imports、bootloader、noarchive から選べます。bootloader は起動時の進行メッセージを出し、「missing importsの診断に使う」と説明されています。imports はモジュールが初期化されるたびに、読み込み元の場所を表示します。
-w(--windowed、--noconsole)を付けたビルドは、標準入出力のコンソールが出なくなります。確認の段階では付けずにビルドして出力を読める状態にし、最後の配布用ビルドで付けるほうが原因を追いやすくなります。
distとbuildを手で編集させない
pyinstaller は、スクリプトと同じ場所に myscript.spec、build、dist を作ります。build には作業ファイルとログ、dist には配布する実行ファイルが入る、と説明されています。どちらも毎回のビルドで再生成される出力なので、Claude Codeが中身を直接書き換えると、その修正は次のビルドで消えます。
残して直接直すべきなのは .spec ファイルです。.spec を編集した後は、スクリプトの代わりに .spec を渡して pyinstaller myscript.spec と実行します。
権限ルールで編集を止める
Claude Codeの権限ルールには、Edit(path) の形でファイル編集を拒否する書き方があります。ルールはgitignoreのパターン記法で、/ から始めるとプロジェクト設定の場所を基準にした位置を指します。.claude/settings.json に次のように書きます。
{
"permissions": {
"deny": [
"Edit(/dist/**)",
"Edit(/build/**)"
]
}
}ファイルのパス規則は Edit(path) と Read(path) だけが参照されます。Write(dist/**) のように書いても受け付けられるだけで効かないため、Edit で書きます。
ただし、このルールが効く範囲には限りがあります。拒否ルールは組み込みのファイルツールと、Bashのうち cat、sed、tee など認識されるファイル操作に効きます。pyinstaller のような別プロセスが dist に書き込むのは止まりません。それが狙いどおりで、Claude Codeの手による書き換えだけを止めつつ、ビルドは通常どおり出力できます。
CLAUDE.mdには完了条件を書く
権限ルールは「させない」ための仕組みで、「どう確かめるか」は伝わりません。完了条件はプロジェクトの CLAUDE.md に書いておきます。CLAUDE.mdは強制される設定ではなく文脈として扱われるため、検証できる具体さで書くのが推奨されています。
## exe化(PyInstaller)
- ビルドは `pyinstaller app.spec` で行い、直す場所は `app.spec` とオプションだけ
- `dist/` と `build/` の中身は編集しない(毎回再生成される出力)
- 完了の条件は `dist/app --self-test` が終了コード0で終わること
- ビルドが通っただけでは完了と報告しないStopフックで「確認前に終わらない」ようにする
CLAUDE.mdは守られないことがあります。止め方を強めたいなら、Stop フックでClaude Codeの終了前にチェックを挟めます。ドキュメントの例では、prompt タイプのフックがタスクの完了をモデルに確認させ、"ok": false が返ると、Claude Codeは作業を続けて reason を次の指示として使います。
この例を起動確認に合わせて書き換えると、次のような形になります(例示です。実際の挙動は手元で確認してください)。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "exe化の依頼であれば、dist内の実行ファイルを起動して終了コードを確認したか調べる。していなければ {\"ok\": false, \"reason\": \"dist内の実行ファイルを --self-test 付きで実行して確認すること\"} を返す。"
}
]
}
]
}
}Stop と PostToolUse のフックは、トップレベルの decision: "block" で止める方式です。判断をモデルに任せる prompt タイプは、確認を実際にしたかどうかの見落としを減らす補助として置く位置づけです。
環境の違いで落ちる場面
起動確認はビルドしたマシンの上で行うため、そこで通っても別のマシンで動くとは限りません。PyInstallerのプラットフォーム別の注意から、読者が踏みやすいものを挙げます。
ビルドした環境の外で起きること
OSをまたぐと作り直し
WindowsとmacOSの両方へ配布するなら、PyInstallerを各OSに入れて、OSごとにビルドします。Claude Codeを動かしているOSの実行ファイルしかできません。
Linuxは古い環境で作る
PyInstallerはlibcを同梱しません。新しいLinuxでビルドしたものは、古いLinuxで動的リンクのエラーになることがあります。対応したい最も古いLinuxでビルドするのが、ドキュメントに書かれた対処です。
onefileは一時フォルダーに展開する
--onefileは起動時に一時ディレクトリーへ展開します。POSIXで一時ディレクトリーがnoexecでマウントされていると、展開した共有ライブラリを読み込めません。環境変数TMPDIRで場所を変えるか、ビルド時に--runtime-tmpdirを使います。
--runtime-tmpdir は「何をしているか分かっているときだけ使う」よう注意書きがあり、POSIXではパス中の ~ や $HOME が展開されません。
Windowsについては、Python 3.5以降が使うVisual C++のランタイムがWindows 10以降のOSに含まれる一方、Windows Vistaから8.1までは更新プログラムの有無に依存します。古いWindowsへ配るなら、起動確認も該当環境で行います。
確認で起動したプロセスを残さない
起動確認で実行ファイルを動かすと、GUIやサーバーのように終わらないアプリでは、確認後もプロセスが残ります。--self-test のように自分で終了するモードを用意しておけば避けられます。Windowsでバックグラウンドのプロセスが残る場合の見分け方は、Claude Codeでfind.exeが残るWindowsの確認と後始末が参考になります。
ビルドと実行を検証ループに入れる考え方は、ほかのビルド対象でも同じです。ゲームサーバーのプラグインでビルドとサーバー起動を確認する例はClaude CodeでMinecraftのPaperプラグインを作るに、CIのビルド失敗を調べさせる例はBitrise MCPをClaude Codeにつないでビルド起動と失敗調査を任せるにあります。
まとめ
exe化の依頼は、完了条件を「ビルドが終わった」から「dist の実行ファイルが起動して終了コード0で終わった」へ置き換えるだけで、取りこぼしの多くを実行時に拾えます。直す場所は .spec とオプションに限り、dist と build は Edit の拒否ルールで手を触れさせない構成が基本です。配布先のOSやlibcが違うなら、確認も配布先の環境に近い場所で行う必要があります。