M1 の launchd ジョブが異常終了していたのに気づけなかった — 未ロードを定期点検する

  • #launchd
  • #自動化
  • #監視
  • #認証

今回やったこと

常駐ジョブについて、plist がリポジトリに存在することと、実機で正しくロードされていることを分けて点検する仕組みを整えた。対象は macOS の launchd で動く無人レーンである。設定ファイルを書いた、PRをマージした、実機がその定義を起動している、直近の実行が成功した、という4つは別々の事実として確認しなければならない。

bash -lc だけでは環境が揃わなかった

ある LLM 利用ジョブは bash -lc で起動され、想定したシェル設定にだけ存在する認証トークンを読み込めなかった。期限切れ認証へフォールバックした結果、401が繰り返され、ログには OAuth の期限切れエラーが大量に残った。plist が存在しているため、設定があるという観測だけでは異常を発見できない。

修正では起動シェルを /bin/zsh-lc に合わせ、bootstrap と kickstart の後に終了コード0と処理成功を実測した。重要なのは、plist の文字列を直したことではなく、launchd がその定義をロードした状態で実行結果まで確認したことだ。

<key>ProgramArguments</key>
<array>
  <string>/bin/zsh</string>
  <string>-lc</string>
  <string>bash /path/to/checkout/scripts/worker.sh</string>
</array>

認証トークン自体はplistやリポジトリに書かない。起動環境から安全に渡せることを確認し、ログには秘密値を残さない。この境界を崩すと、ジョブを直すための記録が新たな漏えい経路になる。

未ロードと実行失敗を別の異常として扱う

scripts/launchd_reconcile.py は、リポジトリの plist と実機のロード状態を突合する。代表的な判定は次のとおりである。

判定意味対応
未ロードリポジトリにあるが、どの対象機にも入っていないbootstrap を確認
幽霊実機にあるが、リポジトリに定義がない由来を確認
乖離実機がリポジトリ外の複製を起動plistと実体を入れ替え
前回失敗ロード済みだが直近の終了コードが非0ログと入力を調査

特に「乖離」は、リポジトリを直しても実機へ届かない状態である。実行中の ProgramArguments がチェックアウト外を指していないかを比較することで、単なる exit code の失敗と区別できる。

0 は「正常終了」とは限らない

点検スクリプトの既定 exit code は、見つけた異常の有無ではなく、点検処理を実行できたかを表す。未ロードを見つけたときに常に exit 1 を返すと、launchd からは番犬自身が恒久的な失敗ジョブに見え、本物の実行失敗が埋もれる。手元やCIで異常を終了コードへ反映したい場合だけ --strict-exit を使う。

通知も同じ考え方で、未ロードなら Discord へ知らせる。通知コマンドの戻り値を捨てず、送信できたときだけ「通知済み」と記録する。通知が失敗したのに状態だけ進めると、異常を一度きりで消費してしまうためである。

やってみてわかったこと

常駐ジョブの点検では、定義・ロード・実体・直近の成功を一枚の「正常/異常」に潰さない方がよい。どこまで確認できたかを分離すると、未ロード、環境差、リポジトリ外の複製、実行時エラーをそれぞれ直せる。

設定ファイルの存在確認は入口にすぎない。無人運用で必要なのは、実機が正しい定義をロードし、その定義が期待する環境で動き、結果が観測できることまでを定期的に確認することである。

試した環境・更新履歴

  • 試した環境: macOS launchd の plist と Python の突合スクリプト
  • 更新履歴: 2026-09-01 初稿
  • 訂正履歴: なし