launchd

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

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

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

📖 目次
  1. 📌 はじめに
  2. 📌 launchd の exit 78 が意味すること
  3. 📌 実際に踏んだパターン3選
  4. 📌 exit コードで原因を素早く絞り込む対応表
  5. 📌 デバッグ手順——これをやれば大抵わかる
  6. 📌 plist チェックリスト
  7. 📌 まとめ

はじめに

この記事では、macOS の launchd で自動化スクリプトを動かそうとしたときに遭遇する「exit 78」の原因と、実際に僕がやらかした失敗ログをもとにした解決手順を書く。

launchd を使いはじめたばかりの人が一番つまずくのが、「スクリプトは手動で動くのに launchd だと動かない」という症状だ。exit 78 はその典型で、原因がわかってしまえば5分で直る。でもわからないまま半日溶かすこともある。実際に溶かした。


launchd の exit 78 が意味すること

まず前提として、launchd が報告する終了コードはジョブ自身が (略) した値をそのまま返す。

exit 78 は (略) で定義されている (略) に対応している。

(コード例は元記事を参照)

「設定エラーで死んだ」という意味だ。ただし、これはスクリプト側が明示的に (略) を呼んだ場合ではない(それなら原因は明白)。launchd 経由でのみ再現し、手動実行では問題ない——そういうケースに限って、環境の差異が原因になっている。


実際に踏んだパターン3選

僕のケースを時系列で書く。同じ症状の人は参考にしてほしい。

パターン1: PATH が端末と launchd で違う

一番多い。手動実行では (略) が見つかるのに、launchd 経由だと (略) で死ぬ。

ターミナルで (略) を打つと (略) が入っているが、launchd のデフォルト PATH は (略) だけだ。

plist に EnvironmentVariables を書いていないと、スクリプトが依存するコマンドが全部見つからなくなる。

(コード例は元記事を参照)

これを追加して (略)(略) で直った。

パターン2: WorkingDirectory が存在しない

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

(コード例は元記事を参照)

確認コマンドはこれだけ。

(コード例は元記事を参照)

存在確認が通らなければディレクトリ名を見直す。(略) の展開も launchd では効かないので絶対パスで書くこと。

パターン3: スクリプト内で import しているモジュールが入っていない仮想環境

これが一番わかりにくかった。ターミナルから実行するときは (略) がアクティブになっているので動く。launchd から呼ばれるときはアクティブになっていないので、(略) とか (略)(略) で死ぬ。

解決策は2つある。

(コード例は元記事を参照)

僕は案Aに統一した。シンプルで依存が少ない。


exit コードで原因を素早く絞り込む対応表

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

exit 78 と 127 はほぼ環境差異の問題。exit 1 になったらスクリプト側のバグを疑う。


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

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

(コード例は元記事を参照)

あるいは StandardErrorPath を plist に追加してエラーログをファイルに吐かせる。

(コード例は元記事を参照)

ロードして実行された直後に (略) を見ると、何が足りないのかが出てくる。

(略) なら venv 問題。(略) なら PATH か WorkingDirectory 問題。このどちらかで exit 78 の9割は説明できた。


plist チェックリスト

設定を見直すときに確認するリスト。

  • [ ] (略) の Python パスが venv 内の絶対パスになっているか
  • [ ] (略) に typo がないか、ディレクトリが実在するか
  • [ ] (略) に PATH を明示しているか(homebrew を使うなら (略) も)
  • [ ] (略) でエラーログを出力しているか
  • [ ] plist を変更した後に (略)(略) しているか

最後の「変更後に reload していない」も地味に多い。変更しても反映されていないのに「直らない」と悩むやつ。


まとめ

exit 78 は「設定が壊れている」というシグナルで、スクリプト自体のバグではないことが多い。launchd はターミナルとは別の環境で動くので、PATH・WorkingDirectory・venv のどれかが欠けていると再現する。

(略) を必ず設定しておけばデバッグ時間が劇的に短くなる。これだけは本当に徹底した方がいい。

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


(コード例は元記事を参照)

👨‍💻
一ノ瀬泰斗
AI自動化エンジニア / Python個人開発者

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