第5部 ループ · 第12回 / 全20回 · 15分くらい
人がいない時間に回すための、5つの部品
第4世代、ループです。第3世代で作った「1回分の仕組み」を、人がいない時間に回します。この回では、最低限そろえておく5つの部品を、macOS の launchd と Claude Code の画面なしのモードを例に見ていきます。
履修 0 / 20 回
この回を読み終えると、できるようになること
- 起動・重ならないための鍵・何度動かしても同じ結果・権限の絞り込み・記録の5つを説明できる
- Mac がスリープしていたとき、定時のジョブがどう動くかを説明し、それに合わせた作りにできる
- 画面なしで Claude Code を動かすときの、道具の許可と記録の残し方を書ける
専門用語は、初めて出たところで括弧に短い説明を付けています。説明が長くなるものには ※番号を付け、ページの終わりの用語メモにまとめました。
1部品1:起動。予定時刻は「早くてもこの時刻」と読む
決まった時刻に動かす係です。Mac なら launchd、サーバーなら cron(Linux などで定時にコマンドを動かす仕組み)、あるいはクラウドの定期実行を使います。
Apple のドキュメントによると、launchd の StartCalendarInterval(毎日何時、のような指定)は、Mac がスリープしていて予定時刻を過ぎた場合、起きたときに動きます。電源が切れていた場合は動きません(cron は、スリープ中の予定を飛ばします)。
つまり、予定時刻は「その時刻ちょうど」ではなく「早くてもその時刻」です。自分の環境では、日曜20時の週の振り返りが、4週続けて月曜の朝に動いていました。そして「今週」を動いた時刻から決めていたので、終わった週ではなく、始まったばかりの週を集計していました。「いつの分か」は、動いた時刻ではなく、対象の期間で決めます。
決まった時刻以外のきっかけで動かすこともできます。たとえば StartOnMount を使うと、ディスクをつないだときに動きます。持ち歩く MacBook のバックアップを、外付けのディスクをつないだときに取る、といった使い方です。
2画面なしで Claude Code を動かす
定時実行の中でAIを使うときは、画面を開かないモードで動かします。Claude Code では claude -p "頼みごと" です。ドキュメントによると、--allowedTools で使ってよい道具を先に許可し、--output-format で出力の形(JSON など)を指定できます。成功すれば終了コード0、失敗すれば0以外で終わります。
誰も見ていない時間に動くので、ここで渡す道具が、そのまま「AIにできることの全部」になります。これが部品4の話につながります。
3部品2:重ならないための鍵(ロック)
前の回が終わらないうちに次の回が始まると、同じ作業が2重に走ります。スリープから起きた直後は、たまっていた予定がまとめて動くので、特に起きやすくなります。
動き始めにロック※1を取り、取れなければ何もせずに終わります。macOS には Linux の flock が無いので、「フォルダを作れた人だけが先へ進める」という mkdir の性質を使うのが手軽です。ロックを外し忘れて、二度と動かなくなる事故も多いので、終わるときに必ず外れる書き方にします(下の例の trap)。
#!/bin/zsh
# ロック:mkdir は、すでにあれば失敗する。作れた人だけが先へ進める
LOCK=/tmp/daily-report.lock
mkdir "$LOCK" 2>/dev/null || { echo "前の回が実行中"; exit 0; }
trap 'rmdir "$LOCK"' EXIT # どう終わっても鍵を外す
claude -p "今日の日報を reports/ に作って" \
--allowedTools "Read,Write" \
--output-format json > "logs/$(date +%F).json" 読み方:|| は「左が失敗したら右を実行」、trap … EXIT は「スクリプトがどう終わっても、最後にこれを実行」という意味です。
4部品3:何度動かしても、結果が同じになる作り
同じ回を2回動かしても、結果が1回分と同じになる性質を、冪等性※2と呼びます。たとえば、
- 「今日の分がもうあれば作らない」
- 上書きではなく、対象の期間を名前に入れて作る(
2026-W41.md) - 対象の期間を引数で渡せるようにする(
--week 2026-W41)
こうしておくと、失敗したらもう一度動かせばいい状態になります。遅れて動いても、2回動いても、あとから作り直しても、同じ名前で同じものができます。運用がぐっと楽になります。
5部品4:権限を絞る
人がいない時間に動くAIには、その作業に必要な道具だけを渡します。日報を書くだけなら、ファイルの読み書きだけ。送信や削除ができる道具は渡しません。
ただし、絞りすぎても困ります。自分の環境では、AIの定期タスクがファイルを書くたびに許可を待って止まり、29時間そのままになっていたことがありました。必要な操作は先に許可し、危ない操作は渡さない、の両方を決めておきます。
6部品5:記録。成功・失敗・何もしなかった、を分ける
何時に動き、何をして、どう終わったか。成功も失敗も残します。記録がないと、壊れたときに「いつから壊れていたか」がわかりません。
大事なのは、「成功した」と「何もしなかった」を分けて記録することです。入力が無かった、送り先が無かった、今日はもう済んでいた。こうした回を「成功」と記録すると、第1回で見た「気づかないうちに壊れる」がそのまま起きます。自分の環境では、記録に done(実際に動いた)・skipped(何もしなかった、理由つき)・failed の3つを分けて書くようにしました。次の回の見張りは、この記録を読んで成り立ちます。
誤解よくある思い違い
毎日2時に登録したのだから、毎日2時に動く
launchd は、スリープ中に過ぎた予定を起きたときに動かします。電源が切れていれば動きません。「いつの分か」は、動いた時刻ではなく対象の期間で決めます。
ロックを取れば、重なりの問題は解決する
ロックを外し忘れると、二度と動かなくなります。どう終わっても外れる書き方と、古いロックに気づく見張りがセットで要ります。
演習手を動かして確かめる
演習1
「毎日23時に、その日の作業ログをまとめる」ジョブがあります。Mac を22時に閉じて、翌朝8時に開いたとき、何が起きるでしょうか。どう直せば正しくなりますか。
答えを見る
launchd は、起きた翌朝8時にジョブを動かします。「その日」を動いた時刻から決めていると、翌日(まだ始まったばかりの日)をまとめてしまい、前の日の分は作られません。
直し方は、対象の日を「動いた時刻」ではなく「まとめていない最後の日」で決めることです。たとえば、まだまとめていない日を記録から探してそれを対象にする、または「23時より前に動いたら前日の分」と決めます。対象の日を引数で渡せるようにしておけば、取りこぼした日をあとから作り直すこともできます。
演習2
上の例のスクリプトで、trap 'rmdir "$LOCK"' EXIT の行を消すと、何が起きるでしょうか。
答えを見る
スクリプトが途中で失敗して終わったとき(claude がエラーになった、など)に、ロックのフォルダが残ります。次の回からは毎回「前の回が実行中」と判断して、何もせずに終わり続けます。しかも終了コードは0なので、成功のように見えます。気づかないうちに壊れる、典型的な形です。
自分は、定時実行の登録ファイルを、リンクではなく実体のファイルで置くようにしています。リンク先の原本が壊れたとき、登録ごと巻き込まれて、バックアップが16日止まったことがあったからです。
用語メモ本文の ※番号の説明
出典確かめた資料(2026年10月時点)
- Apple「Scheduling Timed Jobs」 StartCalendarInterval のジョブは、スリープ中に過ぎたら起きたときに動く。電源が切れていれば動かない。
- Claude Code Docs「Run Claude Code programmatically」
claude -p、--allowedTools、--output-format、終了コード。