M1 の 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 初稿
- 訂正履歴: なし