作業環境・自動化

launchd exit 78 エラーの原因と修正方法——設定ミスを3分で特定する

launchd exit 78 エラーの原因と修正方法——設定ミスを3分で特定する

この記事は約 9 分で読めます

📖 目次
  1. 📌 exit 78 で3時間溶かした話
  2. 📌 exit 78 が「設定エラー」である理由
  3. 📌 私が踏んだパターン3つ
  4. 📌 exit コードと原因の対応表
  5. 📌 デバッグ手順——これをやれば大抵わかる
  6. 📌 plist を書いたら確認する5点
  7. 📌 exit 78 は「環境の差異」シグナルと思えば怖くない
  8. 📌 関連記事

最終更新: 2026-04-28

exit 78 で3時間溶かした話

launchd を使い始めて最初の週、私は exit 78 に3時間費やした。スクリプトはターミナルから動く。plist の書き方も合っている気がする。なのに launchd のパス・環境変数設定で詰まった時の解決策 launchctl start するたびに終了コード 78 が返ってくる——。

exit 78 は「設定が壊れている」というシグナルで、スクリプト自体のバグではない。ターミナルと launchd が別の環境で動いているせいで再現する。原因がわかってしまえば直すのは5分 ターミナルと launchd の環境ズレをデバッグする方法もかからない。でもその「わかるまで」で半日溶かせる。

この記事では、私がパイプライン10本を launchd に乗せる過程で踏んだ失敗パターン launchd で複数パイプラインを安定稼働させる実装例と、デバッグを3分以内に終わらせるための手順を書く。launchd での自動化スクリプト運用の延長として読んでほしい。


exit 78 が「設定エラー」である理由

launchd が報告する終了コードは、ジョブ自身が返した値をそのまま渡す。exit 78 は sysexits.h で定義されている EX_CONFIG に相当する——「設定が壊れている状態で起動しようとした」という意味だ。

ここで注意が要る。これはスクリプト内で sys.exit(78) を呼んだケースの話ではない。launchd 経由でのみ再現し、手動実行では問題なく動く。そういう症状の場合、犯人はほぼ必ず環境の差異だ。

参照: Apple Developer Documentation: Creating Launch Daemons and Agents


私が踏んだパターン3つ

パターン1: PATH がターミナルと launchd で全然違う

一番多かった。ターミナルで which python3 を打つと /opt/homebrew/bin/python3 が返ってくる。でも launchd のデフォルト PATH は /usr/bin:/bin:/usr/sbin:/sbin だけだ。Homebrew のパスが入っていない。

plist に EnvironmentVariables を書いていないと、Homebrew 経由でインストールしたコマンドが全部見つからなくなる。エラーログには env: python3: No such file or directory と出ていた。

<key>EnvironmentVariables</key>
<dict>
    <key>PATH</key>
    <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>

これを追加して unloadload で直った。追記してから動くまで10秒もかからなかった——3時間の大半はここを疑わずに過ごしていた。

パターン2: WorkingDirectory の typo か存在しないパス

plist に WorkingDirectory を指定していて、そのパスが存在しないか typo している場合も exit 78 になる。

<key>WorkingDirectory</key>
<string>/Users/ichinosetaito/Documents/AI_Automation_Base</string>

確認はこれだけ。

ls /Users/ichinosetaito/Documents/AI_Automation_Base

存在確認が通らなければディレクトリ名を見直す。~ の展開も launchd では効かないので絶対パスで書くこと。私は一度 ~/Documents/AI_Automation_Base と書いて30分無駄にした。チルダ1文字で30分——これが2回目の沼だった。

パターン3: venv がアクティブになっていない環境から呼ばれる

これが一番わかりにくかった。ターミナルから実行するときは仮想環境がアクティブになっているので動く。launchd から呼ばれるときはアクティブになっていないので、import anthropic とか import discordModuleNotFoundError で死ぬ。エラーログを仕掛けていなかったので、最初は何が起きているのかすら見えなかった。

解決策は venv の Python を直接指定するか、シェルスクリプトで source activate してから呼ぶかの2択で、私は前者に統一した。

<!-- venv の Python を直接指定する(私はこちら) -->
<key>ProgramArguments</key>
<array>
    <string>/Users/ichinosetaito/Documents/AI_Automation_Base/.venv/bin/python3</string>
    <string>/Users/ichinosetaito/Documents/AI_Automation_Base/01_Scripts/post_to_bsky.py</string>
</array>

シェルラッパー経由だと起動のたびにシェル1本分のオーバーヘッドが乗る。数十ms の差でしかないが、パイプライン10本が並走すると地味に積み上がる。venv の Python を直接指定する方がシンプルで依存も少ない。


exit コードと原因の対応表

exit コード 意味 よくある原因
78 EX_CONFIG(設定エラー) PATH・WorkingDirectory・venv 未設定
127 command not found インタープリタのパスが間違い
1 一般エラー スクリプト内で例外発生
126 実行権限なし chmod +x していない
0 正常終了

exit 78 と 127 はほぼ環境差異の問題で、exit 1 になったらスクリプト側のバグを疑う。私のパイプライン10本で集計すると、exit 78 が障害全体の約6割を占めていた。残りの約2割が exit 1 で、127 や 126 は稀だった。


デバッグ手順——これをやれば大抵わかる

ログを見る。まずここから。

log show --predicate 'subsystem == "com.apple.launchd"' --last 1h | grep -i error

ただしこれだと出力が粗い。plist に StandardErrorPath を仕込んでおく方が圧倒的に速い。

<key>StandardOutPath</key>
<string>/tmp/myjob.out.log</string>
<key>StandardErrorPath</key>
<string>/tmp/myjob.err.log</string>

ロードして実行された直後に cat /tmp/myjob.err.log を見ると、何が足りないのかが出てくる。ModuleNotFoundError なら venv 問題。No such file or directory なら PATH か WorkingDirectory 問題。このどちらかで exit 78 の9割は説明できた。

私が最初の3時間で何をしていたかというと——StandardErrorPath を設定していなかったので、何も見えない状態で plist の文法ミスを探し続けていた。ログの出口を作る、それだけで世界が変わる。


plist を書いたら確認する5点

毎回このリストを一周する癖をつけてから、exit 78 で詰まる時間がほぼゼロになった。

  • [ ] ProgramArguments の Python パスが venv 内の絶対パスになっているか
  • [ ] WorkingDirectory に typo がないか、ディレクトリが実在するか(ls で確認)
  • [ ] EnvironmentVariables に PATH を明示しているか(Homebrew を使うなら /opt/homebrew/bin も含める)
  • [ ] StandardErrorPath でエラーログを出力しているか
  • [ ] plist を変更した後に launchctl unload ~/Library/LaunchAgents/com.myjob.plistlaunchctl load ~/Library/LaunchAgents/com.myjob.plist しているか

最後の「変更後に reload していない」も地味に多い。変更しても反映されていないのに「直らない」と悩むやつ——私は2回やった。unloadload をセットで実行するワンライナーをシェルの alias に登録してからは起きていない。


exit 78 は「環境の差異」シグナルと思えば怖くない

exit 78 が出たとき、スクリプトを疑うより先に環境を疑う。PATH・WorkingDirectory・venv のどれかが欠けているだけで、コード自体は正しいことがほとんどだ。

StandardErrorPath を必ず設定しておけばデバッグ時間が劇的に短くなる。私の場合、設定前は原因特定まで平均30分かかっていたのが、設定後は3分以内で済むようになった。体感ではなく実測の値だ。

今はパイプラインが10本以上 launchd で動いているが、exit 78 で詰まるのはたいてい新しい plist を書いたときの最初の数分だけだ。慣れたら怖くない。✨


一ノ瀬泰斗のアバター
一ノ瀬泰斗
AI自動化エンジニア / Python個人開発者

Claude Code × Ollama × launchd で SNS・ブログ・KDPを全自動化。実測データと失敗談を軸に、月5万円収益化のリアルな記録を発信中。

💬 自動化の相談・小規模受託も受付中:「launchd で毎朝 AI が動く仕組みを作りたい」「KDP の自動出版を組みたい」など、X (@taito_automate) の DM からお気軽にどうぞ。


関連記事

✨ AUTHOR'S KDP BOOKS

かかる人向ケ、10分でわかるAI自動化入門

Claude Code / Ollama / launchd の実践テクニックをコンパクトにまとめたシリーズ。非エンジニアの会社員向けに書いてます。

Amazonで見る ›

✨ FOLLOW ME

AI自動化の実験・失敗・実測データを毎日発信中

𝕏 フォローする