結論から書きます。スクリプトをデスクトップ・書類・ダウンロードのいずれかに置いていませんか。 そこが原因です。
macOSのプライバシー保護機能により、launchdから起動されたプロセスはこれらのフォルダを読み取れません。ターミナルから手動で実行すると動くため、原因の特定が非常に難しいトラブルです。
この記事では、原因と確実な対処法、そして「自動化が止まっていることに気づけない」というより本質的な問題への対策を解説します。
症状
典型的な症状は次の3点です。
1. 手動では動く
./run_prediction.sh # 正常に動く
2. launchd経由だと失敗する
StandardErrorPath に指定したログには、こう出ます。
/bin/zsh: can't open input file: /Users/【ユーザー名】/Desktop/経理/run_prediction.sh
ファイルは確実に存在し、実行権限もあります。
ls -l run_prediction.sh
# -rwxr-xr-x 1 user staff 1366 6 22 15:04 run_prediction.sh
3. 終了コードが127
launchctl list | grep keiri
# - 127 com.keiri.prediction
終了コードの読み方
launchctl list の出力は左から PID / 最後の終了コード / ラベル です。
| 表示 | 意味 |
|---|---|
12345 0 com.example |
実行中(PIDあり) |
- 0 com.example |
正常終了した |
- 127 com.example |
コマンドが見つからない場合など。原因はエラーログと併せて確認 |
- 126 com.example |
実行権限がない |
- 1 com.example |
スクリプト内でエラー終了した |
終了コード127だけでは、スクリプトがどこまで動いたかは判断できません。 スクリプト内の途中のコマンドが見つからず、127で終了する場合もあります。今回のようにログに can't open input file と出ている場合は、指定した入力ファイルのパスとアクセス権を確認します。
例えば echo STARTED の後に存在しないコマンドを実行すると、最初の処理で STARTED を表示した後でも終了コードは127になります。Bashとzshでこの動作を確認しています。
原因:macOSのプライバシー保護(TCC)
macOSは以下のフォルダを保護しており、アクセスには明示的な許可が必要です。
- デスクトップ
- 書類(Documents)
- ダウンロード
- iCloud Drive、外部ボリューム など
ターミナルから手動実行すると動くのは、ターミナル.appにアクセス許可が与えられているためです。
一方、launchdから起動された /bin/zsh にはその許可がありません。結果として、ファイルの存在すら確認できずに終了します。エラーメッセージが「権限がない」ではなく「ファイルが開けない」になるため、パスの記述ミスを疑って時間を溶かしがちです。
対処法①:保護対象外の場所へ移す(推奨)
最も確実で安全な方法です。ホームディレクトリ直下など、保護されていない場所に置きます。
mkdir -p ~/scripts/keiri
mv ~/Desktop/経理/*.sh ~/Desktop/経理/*.py ~/scripts/keiri/
plist内のパスを新しい場所に書き換えます。
<key>ProgramArguments</key>
<array>
<string>/bin/zsh</string>
<string>/Users/【ユーザー名】/scripts/keiri/run_prediction.sh</string>
</array>
<key>StandardOutPath</key>
<string>/Users/【ユーザー名】/scripts/keiri/launchd_out.log</string>
<key>StandardErrorPath</key>
<string>/Users/【ユーザー名】/scripts/keiri/launchd_err.log</string>
読み込み直します。
launchctl unload ~/Library/LaunchAgents/com.keiri.prediction.plist
launchctl load ~/Library/LaunchAgents/com.keiri.prediction.plist
注意:スクリプトが読み書きするデータファイル(xlsx等)も一緒に移動してください。スクリプト本体だけ移しても、処理対象がデスクトップにあれば同じ理由で失敗します。
対処法②:フルディスクアクセスを与える(非推奨)
「システム設定 → プライバシーとセキュリティ → フルディスクアクセス」で /bin/zsh を追加する方法もあります。
ただし推奨しません。 これは「zshで実行されるすべてのスクリプト」に保護フォルダへの権限を与えることを意味します。特定の自動化のために、システム全体の防御を下げることになります。
動作確認の方法
修正後、次の実行予定日を待たずに確認します。
即座に実行して確かめる
launchctl kickstart -k gui/$(id -u)/com.keiri.prediction
実行後、終了コードを確認します。
launchctl list | grep keiri
# - 0 com.keiri.prediction ← 0なら成功
数分後の時刻を設定して待つ
StartCalendarInterval を一時的に数分後に設定し、実際にlaunchdが起動するかを確認する方法もより確実です。kickstart は手動起動に近いため、スケジュール起動そのものの検証にはこちらが向きます。
より本質的な問題:静かに止まる
ここからが重要です。
上記の対処で今回の障害は直ります。しかし、次に別の理由で止まったとき、また気づけません。
自動化の失敗はサイレント障害です。
- 通知が来れば気づく
- 通知が来なければ、何も起きない
「何も起きないこと」は意識に上りません。しかも自動化した本人は「毎月動いているはず」と思い込んでいるため、発見が遅れます。
実際、筆者は数ヶ月間、一度も自動実行が成功していないことに気づきませんでした。 実行ログに記録が2件しかないことに気づいたのは、この件とは無関係に記事を書こうとしてログを開いたときです。
サイレント障害を検知する3つの設計
① 成功も通知する
結果が出たときだけ通知していると、「通知がない」状態の原因が切り分けられません。
処理の開始時にも通知を送るようにします。
# 処理の冒頭
curl -s -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"⏳ 着地予想の処理を開始しました\"}" "$HOOK"
これで次のように判断できます。
| 受信状況 | 判断 |
|---|---|
| 開始も結果も来ない | 起動していない(launchdの設定・権限の問題) |
| 開始は来たが結果が来ない | 処理の途中で失敗(スクリプト内のエラー) |
| 両方来た | 正常 |
通知は増えますが、静かに壊れているよりはるかにマシです。
② 「最終実行日時」を目に入る場所に置く
ログファイルは、開かなければ気づけません。
実行のたびに最終実行日時を上書きするファイルを作ります。
date '+%Y-%m-%d %H:%M' > ~/scripts/keiri/LAST_RUN.txt
追記(>>)ではなく上書き(>)にするのがポイントです。1行しかないファイルなら、日付を見るだけで異常が分かります。中身を読む必要すらありません。
③ 終了コードを定期的に確認する
launchctl list | grep -E "^-\s+[^0]"
終了コードが0以外のジョブだけを抽出します。これを別の定期実行に組み込めば、失敗の検知を自動化できます。
原則:自動化は「作った時点」ではなく 「自動で1回動いたことを確認した時点」 で完成です。
手動実行の成功は、動作確認になっていません。カレントディレクトリ・環境変数・権限が、すべて異なるためです。
そもそもMacで動かすべきか
TCCの問題を解決しても、個人のMacで定期実行する限り残る制約があります。
| 制約 | 内容 |
|---|---|
| スリープ | Macがスリープ・電源オフだと実行されません。ノートを閉じて帰った日の朝は動かない |
| ログイン状態 | LaunchAgentはユーザーがログインしていることが前提 |
| 持ち運び | ネットワーク環境が変わると外部連携が失敗する場合がある |
launchd には、スリープで実行できなかったジョブを復帰後に実行する挙動もありますが、「決まった時刻に確実に」を保証するものではありません。
毎日・毎月、決まった時刻に動いてほしい処理は、常時稼働している環境に置くのが安定しやすい方法です。VPSは小さなプランから始められますが、必要な容量は処理内容で変わります。まず手元のMacでメモリ使用量と実行時間を確認し、余裕のあるプランを選びます。
Macを閉じている時間にも処理を動かしたい方へ
必要になってから、常時稼働の環境へ移す
- Macのスリープ・電源オフに左右されない
- 集計と通知なら、小さな構成から検討できる
- 最初はMacで試し、確実な時刻実行が必要になったら移す
当サイト運営者は、現在このVPSを日常業務で常用していません。契約前に必要な容量・料金・運用方法をご確認ください。
Linuxサーバーに移す場合、定期実行は launchd ではなく cron または systemd timer になります。移行手順は別記事で解説します。
まとめ
- 手動で動くのにlaunchdで動かないなら、まず設置場所を疑う。 デスクトップ・書類・ダウンロードは保護対象
- 終了コード127だけで「1行も実行されていない」とは判断しない。 エラーログと併せて、失敗したコマンドや入力ファイルを確認する
- 対処は保護対象外への移動が最も安全。フルディスクアクセスの付与はシステム全体の防御を下げる
- データファイルも一緒に移すこと。スクリプトだけ移しても失敗する
- 本質的な問題はサイレント障害。成功通知・最終実行日時・終了コード監視の3点で検知できるようにする
- 確実性を求めるなら、個人のMacではなく常時稼働環境へ
あわせて読みたい
- Macの自動実行:設定の基本から見直す
設定項目と動作確認を順に読みたい方へ。リンク先の記事には広告が含まれます。 - CLAUDE.mdとは何か|AIへの指示をファイルで管理する
AIへの前提説明を1回で済ませるための設定ファイル。 - Googleスプレッドシートの売上から月末着地予想を自動計算しSlackに投稿する方法
実際に運用している仕組みをコード付きで解説。 - 経理はスプレッドシート自作と会計ソフト、どちらがいいか
自作の限界と、会計ソフトに任せるべき範囲の線引き。


コメント