macos-watchdog について
macos-watchdogは、macOSの監視タスクや永続ジョブの設計、導入、診断に使うスキルです。前提状態の確認、抑制的な修復、クールダウン、ライフサイクル適合、実運用での受け入れ確認を重視します。
主な機能
- 01監視トリガーの選定
- 02静かな監視の安全契約
- 03段階的クールダウン方式
- 04launchdジョブの導入と読み戻し
- 05停止と恒久廃止の手順
活用シーン
- 01macOSタスクの監視設計
- 02通知の連発やアプリ再起動の診断
- 03永続Pythonジョブの導入・検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ daymade/ claude-code-skills --skill macos-watchdogインストール後、Claude Code で/macos-watchdogを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/launchagent.template.plistevals/evals.jsonevals/iteration-1/eval-2-audit/fixture/broken-heal.shbroken-watchdog.plistreferences/alert-discipline.mdevent-driven-observers.mdlaunchd-plist-reference.mdnative-job-readback.mdprobe-cost.mdquiet-watchdog-patterns.mdscripts/inspect_native_job.pynew-launchagent.shwatchdog-cooldown.shtests/test_inspect_native_job.py詳細
翻訳後に原文が更新されています。最新の内容は原文をご確認ください。
macOS ウォッチドッグ
監視対象の問題に応じてイベントトリガーまたは定期トリガーを選択し、修復を行う前に quiet-watchdog contract を適用します。
ウォッチドッグのライフサイクルを、その前提となる状態に結び付けます。状態を修復できない場合(WiFi の破損、ユーザーによる対象アプリの終了、前提状態の消失など)は、人が無効化するのを待たずに停止します。
エントリーポイントの判断ツリー
| 状況 | 参照先 |
|---|---|
| 新しいウォッチドッグを一からインストールする | references/event-driven-observers.md でトリガーを選択し、§ デプロイと§ quiet-watchdog contract を参照します |
| 既存のウォッチドッグが誤動作する(スパム、アプリの再起動、過剰な処理) | § quiet-watchdog contract を参照し、違反している条項を診断します |
| ジョブを停止/無効化/再起動する | § Stop semantics を参照します |
| plist キーの詳細(KeepAlive の形式、ドメイン、ログ記録、リソース制限) | references/launchd-plist-reference.md を参照します |
| 情報源に対してイベント駆動と定期実行のどちらを選ぶか | references/event-driven-observers.md § Select event vs periodic を参照します |
| イベント駆動型オブザーバー(WatchPaths、FSEvents、inotify 形式)をデプロイ/受け入れする | references/event-driven-observers.md § Deployment SOP と§ Acceptance SOP を参照します |
| plist の編集が反映されているか確認する、またはジョブの argv/明示的な ENV/設定済みパス/スケジューリングキーを比較する | 正確な期待値の plist を指定して scripts/inspect_native_job.py を実行し、結果、スケジューリングフラグ、情報源の選択ルールについて references/native-job-readback.md を読みます |
| すでに認可済みの永続カレンダージョブを、配信期限付きで設定または復旧する | references/launchd-plist-reference.md § Calendar deadlines and recovery を参照し、期限超過の作業と実際の配信状況を個別に確認します |
| クールダウン/バックオフ/通知スロットリングのパターンと、匿名化された実例 | references/quiet-watchdog-patterns.md を参照します |
| SRE アラートの階層化(ページ通知とチケット、疲労度の数値) | references/alert-discipline.md を参照します |
| Script Editor の下にウォッチドッグのアラートが表示される、または送信元が不明 | references/alert-discipline.md § Message content を参照し、配信されたカードを確認します |
quiet-watchdog contract
ウォッチドッグをリリースまたは承認する前に、以下のすべての条項を満たす必要があります。各条項は、実際にウォッチドッグが違反したことを受けて設けられています。
1. 前提状態の自己チェック — 実行すべき仕事があるかを把握する
スクリプトは実行のたびに、最初の処理として、その存在を正当化する状態がまだ成立しているかを確認します。成立していない場合は、修復も通知も副作用も発生させず、静かに終了します。
- プロキシ修復ウォッチドッグは、まずプロキシアプリが実行中かを確認します。ユーザーが終了した場合は、そのサイクルをスキップします。
- 「設定が元に戻ったか」を監視するウォッチャーは、監視対象の設定状態を確認します。すでに切り替わっている場合は、誤解を招く通知をもう一度送るのではなく、自ら停止します。(実例:前提条件が解消された後も、停止指示がなかったため、復旧ウォッチャーが 2 時間にわたって動作し続け、誤った通知を 3 件送信しました。)
2. まず修復し、障害が継続した場合にのみ通知する
検出は毎サイクル正確に行いますが、影響の大きいアクションは、障害が N サイクル連続して続くまで保留します(待機モード)。理由:不安定なチェーンは数分で自動復旧するため、自然に収束する一時的な障害で強制再接続すると、かえって害になります。N を決める前に、システムの実際の自動復旧時間を測定してください(あるチェーンを94分間観測したところ、自動復旧は3分以内だったため、5分間隔で N=2 サイクルとしました)。
トリガーが複数の独立したソースを集約して判定する場合(単一のヘルスチェックではない場合)、ソース間の合意率は N とは別の第2のパラメーターです。「すべてのソースの一致」をデフォルトにしないでください。全か無かのゲートでは、ウォッチドッグが検出すべき、相関はあるものの部分的なインシデントを見逃します。実際の障害で、すべてのソースが同時に停止することはほとんどないためです(実例:9つのソースからなるコレクションヘルスのゲートは、9/9の障害を条件としていたため、6/9で発生中のインシデントを見逃しました。実際のインシデントデータに基づき、ソースの60%以上をしきい値として再調整したところ検出できました。また、3つのフィクスチャを使った0%/11%/100%のテストで、単一ソースの一時的な障害には引き続き耐えられることを確認しました)。どの程度の広がり(何割のソースが一致すべきか)を求めるかは、深さ(N)と同様に実際の過去の障害データに基づいて調整してください。「十分厳しそう」という感覚で選んだしきい値は、調整ではなく推測です。
エスカレーションの段階(低コスト → 影響大):状態を更新 → 接続を再起動 → リモート修復。次の段階に進む前に、それぞれの結果を確認します。
3. エスカレートする自動クールダウン — 修復不能な環境では通知しない
修復の全段階が失敗した場合、その環境はウォッチドッグでは修復できません(Wi-Fi の故障、キャプティブポータル、上流の停止など)。単純な動作、つまり「一定間隔で修復の全段階を再実行し、通知を送り続ける」ことは、まさに「ウォッチドッグが10分ごとにアプリを再起動し続ける」状態です。
ThrottleInterval では解決しません。プロセスの再生成を抑制するもので、固定間隔でバックオフはなく、終了コード 0 で終了しながら大量の通知を送るジョブには何も効果がありません。クールダウンはアプリケーション層で実装する必要があります。
- 失敗した連続ラウンド数を状態ファイルに記録します。
- 失敗したラウンドごとに、段階的に延長する期間(例:30分 → 2時間 → 6時間。最後の段階は繰り返します)待機します。
- クールダウンに入るときに1回通知し、クールダウン中は通知しません。段階の期限が切れたらラウンドを1回再試行し、実際に修復できた場合はカウンターとクールダウン状態をクリアします。
- 手動の
pause [duration]コマンドと TTL 状態ファイルを代替手段として用意します。ただし、自動処理は人によるコマンド実行なしで動作しなければなりません。ユーザーがコマンドを覚えておく必要のある無効化機能は、機能とは言えません。
再利用可能な実装:scripts/watchdog-cooldown.sh(読み込むと、paused_any、record_exhausted、clear_exhausted、cmd_pause/cmd_resume が利用できます)。
4. ユーザーが明示的に終了したアプリを決して復活させない
macOS では、アプリが起動していないときに open <url-scheme> を実行するとアプリが起動し、-g を付けずに open を実行すると前面に表示されます。URL スキーム(または open -a、GUI アプリの再起動)を使って修復するウォッチドッグは、ユーザーには「終了したのにまた起動した」と受け取られます。
このようなアクションはすべてゲートしてください。スキームを呼び出す前に対象プロセスが起動していることを確認し、正当なアクションでもウィンドウが表示されないよう -g を渡します。ユーザーが修復の途中でアプリを終了した場合は、エスカレーションを中止してください。後処理のトラップも同じゲートを守らなければ、「終了時に接続を確保する」フォールバックがアプリを復活させてしまいます。
デプロイ(問題を招きやすい仕組み)
既存の定期オブザーバーをデプロイまたは統合する前に、
references/probe-cost.md を適用してください。観測処理の作業量に上限を設け、
ネイティブの起動を一通り完了させてください。既存のスクリプトをインポートしただけでは、
そのコストや判定が保証されたことにはなりません。
設定が有効になったと宣言する前に、
references/native-job-readback.md を使ってネイティブの読み戻しヘルパーを実行してください。
ジョブ自身のブロックにある明示的な ENV を読み取り、
デフォルトまたは継承された環境セクションから最初に一致したキーを取得しないでください。
登録状態やプロセスの状態とは別に、業務上の処理が一通り完了したことを確認してください。
- 配置場所:ユーザーエージェント →
~/Library/LaunchAgents/(GUI セッションのコンテキスト:アプリを開いたり、通知を表示したりできます)。システムデーモン →/Library/LaunchDaemons/(root、GUI にはアクセスできません)。慣習ではなく、ジョブがユーザーの GUI セッションを必要とするかどうかで選択してください。 - plist:定期ジョブの場合は
assets/launchagent.template.plistを基にします。イベントジョブの場合はreferences/event-driven-observers.mdとその plist リファレンスに従ってください。plutil -lintで検証してください。ProgramArgumentsの要素 0 には絶対パスを指定し、シェルの PATH 継承に依存しないでください。 - ロード/再ロード:既存ジョブを置き換えたり停止したりする前に、
references/event-driven-observers.md§ Deployment SOP にある所有権、処理の実行中状態、保留中の意図に関する確認を適用してください。認可済みでアイドル状態のジョブは、launchctl bootstrap gui/$(id -u) <plist>でロードします。plist を編集した後は、bootoutを実行してからbootstrapを実行してください。所有権がありアイドル状態のジョブを一度実行するには、launchctl kickstart gui/$(id -u)/<label>を使います。-kは既存のインスタンスを終了するため、通常のテストには使いません。 - ログ:
StandardOutPath/StandardErrorPathは必須です(指定しないとエラーが消えてしまいます)。さらに、スクリプト内でログをローテーションしてください(上限は約 1 MB)。 - 冪等性ガード:デプロイを再実行しても二重にインストールされないようにしてください。定期ジョブに限り、上記の置き換え確認の後に
scripts/new-launchagent.sh <label> <script> <interval>を使います。ロード済みのジョブがある場合、これは boot out できます。イベントジョブにはイベント SOP を使ってください。どちらの場合も、ネイティブヘルパーを通じてロード済み設定を確認し、処理の完了は別途確認してください。 - TCC/フルディスクアクセス:保護されたファイルを読み取る LaunchAgent には、実効的な権限主体に対する有効な許可が必要です。
macos-permissionsを使って TCC の帰属を調べ、実際のジョブから保護対象の読み取りを検証してください。シェルのインタープリターのパスだけでこれが決まるわけではありません。 - デフォルトでバッチ処理をスロットリング:作業(リプレイ、ファジング、バッチスキャン、並列 API 呼び出し)を起動するウォッチドッグループには、後から追加する最適化ではなく、デフォルトのパラメーターとして明示的なレート上限が必要です。マシンにとって、スロットリングされていないループと暴走プロセスは区別できません(実例:スロットリングされていないテストリプレイが 7 分間に毎秒 1,041 プロセスをフォークし、ダイ温度が 83 °C まで上昇しました)。
- ブラウザーの副作用の受け入れ:データの取得に成功しただけでは、ブラウザーを利用するウォッチドッグの準備が整ったことの証明にはなりません。デプロイ担当者は、タスク所有のスペースとページの予算を宣言したうえで、初期化の挿入やクリーンアップに失敗した場合も含め、複数回のラウンドにわたってそれらを独立して棚卸しする必要があります。件数はそのライフサイクル予算の範囲内に収めてください。また、ユーザー所有および発信元不明のページは常に保護してください。タスクでフォーカスを奪わないことが求められる場合は、これらのラウンド中にフォアグラウンドアプリと可視ウィンドウを独立して観測してください。CDP の成功は、フォーカスが維持された証拠にはなりません。ego lite のライフサイクル、ロック、所有権の競合、クリーンアップの読み戻しについては、インストール済みの場合、名前付き Skill
ego-lite-useを読み込んでください。それ以外の場合は、同じ予算と読み戻しの要件を満たしながら、所有するブラウザツールに記載されたライフサイクルを使用してください。リソースの観測がない場合、またはフォーカスを奪わないことが求められる場合にフォーカスの観測がない場合は、受け入れ確認は未完了です。
停止に関するセマンティクス(非推奨の落とし穴)
ジョブを停止または置き換える前に、§ デプロイに記載された所有権とアクティブな作業の確認、および現在のタスクの認可境界を適用してください。
| 意図 | コマンド |
|---|---|
| 今すぐ停止し、後で再度ブートストラップできるようにする | launchctl bootout gui/$(id -u)/<label>(デーモンの場合:sudo launchctl bootout system/<label>) |
| ログイン後の読み込みを防止する(読み込み済みのジョブは別途停止) | launchctl disable user/$(id -u)/<label>(解除:enable) |
| 編集してから再読み込みする | bootout → plist を編集 → bootstrap |
非推奨の load/unload ではなく、bootstrap/bootout を使用してください。後の読み込みでもジョブを無効のままにする必要がある場合は、disable を適用してください。インスタンスを停止するだけでは、この永続的なオーバーライドは設定されません。
トラブルシューティング早見表
| 症状 | 最初に確認する項目 |
|---|---|
| 「終了したアプリが再起動する」 | URL スキーム/open の呼び出しに、プロセス稼働確認ゲート(条項 4)がない |
| 「数分おきに同じ修復を繰り返す」 | 試行回数上限到達後のクールダウンがない(条項 3)。また、ラダーの失敗時にデッドカウンターがリセットされないことも確認する |
| 「実際には障害が発生しているのに、正常と報告される」 | ヘルスチェックで確認できるのは、プローブ対象の経路だけです。プローブが 1 つ成功しても、すべてのプレーンが正常とは限りません(もう一方のプレーンのプローブを追加する) |
| 「bootout が効かない/また戻ってくる」 | ドメイン/サービスの対象が正確か、また誰が再度ブートストラップしたかを確認します。永続的に停止するには、§ 停止に関するセマンティクスに従ってください |
| 実行が一度もない | StandardErrorPath がないため、失敗を確認できない状態です。続けて log show --predicate 'process == "launchd"' --last 15m を実行します |
| 「ログの mtime は新しい」のに、ジョブは実際には失敗している | out.log がすべて成功を示していても、正常とは限りません。失敗した実行では何も書き込まれないことがあります(set -e と、成功時のみ出力する print)。そのため、最終成功時刻はいつまでも新しいままです。成功側の鮮度(パターン 7)ではなく、失敗時の経路(err.log/最終終了ステータス)を確認してください |
| 対話的には動作するが、launchd では失敗する | 誤ったインタープリターに対する TCC/FDA、ProgramArguments 内の PATH の前提 |
| 履歴が増えるにつれて観測に時間がかかる | references/probe-cost.md を使って、コールド、ウォーム、差分の各処理を監査します。バイト列をキャッシュしても、解析やクエリに上限が設けられるわけではありません |
各条項の詳細と、個人情報を除去した実例については、references/quiet-watchdog-patterns.md を参照してください。