AI活用

Macのlaunchdで定期実行する方法|毎朝9時にclaudeを動かす設定と、Documentsで止まる落とし穴【2026年】

編集責任者佐藤翔太株式会社UrakaTA 代表取締役記事はAIが社内の記録から下書きし、別のAIが公式情報で照合、公開後に編集責任者が確認しています。
大きな目覚まし時計の横で、会社員がノートPCを開き、机の上の小さなロボットが朝の仕事を始めているイラスト
イラスト:AIで作成

この記事の結論

Macのlaunchdで定期実行するなら、LaunchAgentsにplistを置きlaunchctlで登録します。僕の会社では2026年5月27日から毎朝9時にclaude CLIを動かしています。

目次9項目
  1. Macのlaunchdで定期実行する仕組み:plistを置いてlaunchctlで登録する
  2. 毎朝9時にclaude CLIを動かすlaunchdの構成(2026年5月27日から稼働)
  3. launchctlで登録・確認・手動実行するコマンド
  4. Macが眠っている間のlaunchd:スリープ中は起きてから1回、電源オフ中は動かない
  5. Documentsの同期がlaunchdで「Operation not permitted」になった(2026年7月19日)
  6. launchdから書類フォルダを読ませる3つの逃げ道(2026年10月の実測)
  7. launchdでclaude CLIを無人で回すときの注意
  8. まとめ
  9. よくある質問

【無料・メールだけ】Claude Code 2daysセミナー(第二の脳構築講座)の次回開催の案内を受け取る →

Macのlaunchdで定期実行をするとき、僕の会社に必要だったのは、LaunchAgentsに置く設定ファイル1枚と30行強のスクリプトでした。2026年5月27日から、このしくみで毎朝9時にclaude CLIを動かしています。ただ、書類フォルダを読む処理は同じやり方では動かず、別の形に切り替えました。

最初のテストは5月27日0時14分からの約3分で、受信約50件のメールを整理し終えました。逆に7月19日には、社内ノートの自動同期が Operation not permitted で止まっています。

この記事では、plistの書き方、launchctlでの登録、眠っている間の扱い、書類フォルダで止まったときの逃げ道まで書きます。「Macで決まった時刻に動かしたいけれど、cronでいいの?」と迷っている方は、最初の2章で全体がつかめます。

Macのlaunchdで定期実行する仕組み:plistを置いてlaunchctlで登録する

Macのlaunchdで定期実行するには、時刻を書いた設定ファイル(plist)を所定のフォルダに置き、launchctlで登録します。プログラムを書く必要はなく、決まった形のXMLを1枚用意すれば足ります。

ここでいうlaunchdとは、macOSの中でほかのプログラムの起動を受け持つしくみです。Appleの開発者向け文書は、時刻で動く処理を足すならlaunchdが望ましいとしています。

cronは非推奨、Appleはlaunchdを勧めている

Appleの文書には、cronはまだ動くものの推奨されず、launchdに置き換えられたと書かれています。共有のcrontabを書き換えるため、プログラムから足すべきではないとも書かれています。

僕の会社で定時に動かしている処理も、launchdで書いています。スリープ中の扱いがcronと違う点は、後の章で表にしました。

LaunchAgentsとLaunchDaemonsの違い

plistの置き場所は、誰の権限で、いつ動かすかで変わります。macOS付属の説明(man launchd.plist)に載っている置き場所を、用途と並べます。

置き場所中身向く用途
~/Library/LaunchAgentsそのユーザーが置く、ユーザーごとのエージェント自分のMacで、自分の作業を定時に回す
/Library/LaunchAgents管理者が置く、ユーザーごとのエージェント社内の全員のMacに同じ処理を配る
/Library/LaunchDaemons管理者が置く、システム全体のデーモンログインしていなくても動かしたい処理

Appleの文書では、ユーザーのエージェントは、そのユーザーのログイン中だけ動くとされています。僕なら、まず自分のホームのLaunchAgentsから始めます。管理者権限が要らず、失敗しても自分の環境で閉じるからです。

毎朝9時に動かすplistの例

下は、毎朝9時0分にシェルスクリプトを動かす例です。ラベルもパスも架空の名前にしてあるので、自分の環境に置き換えてください。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.example.morning-task</string>
  <key>ProgramArguments</key>
  <array>
    <string>/bin/bash</string>
    <string>/Users/yourname/Scripts/morning-task/run.sh</string>
  </array>
  <key>StartCalendarInterval</key>
  <dict>
    <key>Hour</key>
    <integer>9</integer>
    <key>Minute</key>
    <integer>0</integer>
  </dict>
  <key>StandardOutPath</key>
  <string>/Users/yourname/Library/Logs/morning-task/out.log</string>
  <key>StandardErrorPath</key>
  <string>/Users/yourname/Library/Logs/morning-task/err.log</string>
</dict>
</plist>

Label と ProgramArguments は、Appleの文書で必須とされている2つです。Label はジョブを見分ける一意の名前で、ProgramArguments は起動するコマンドとその引数を並べます。

StartCalendarInterval は時刻の指定で、書かなかった項目は「毎回」として扱われます。上の例では日も曜日も書いていないので、毎日9時0分です。

StandardOutPath と StandardErrorPath は、出力の書き出し先です。Appleの文書は、出力を捨てずにこの2つで受けるよう勧めています。動かなかったときに、まず見る場所になります。

毎朝9時にclaude CLIを動かすlaunchdの構成(2026年5月27日から稼働)

僕の会社の朝の定時処理は、launchd、シェルスクリプト、claude CLIの3段で動いています。launchdが9時にスクリプトを起こし、スクリプトがclaude CLIに指示を渡す形です。

稼働を始めたのは2026年5月27日の0時18分で、それから毎朝9時(日本時間)に動いています。Claude Codeにすでにつないでいたメールの接続を、そのまま流用しました。

3つの案から、シェルとclaude CLIを選んだ理由

作る前に、3つの案を比べています。クラウドで動くルーティンは、当時は使いたい連携先につなげられませんでした。

メールの公式APIを自前で呼ぶ案は、認証情報の取得と更新の管理が必要で、コードの量も増えます。シェルとclaude CLIなら、既存の接続を流用でき、スクリプトは30行強で済みました。

案当時の判断理由
クラウドのルーティン見送り使いたい連携先につなげられなかった
APIを自前で実装見送り認証の管理が要り、コードが多い
launchd+シェル+claude CLI採用既存の接続を流用でき、30行強で済む

Macが動いていないと処理も動かない点は、同じメモに既知の制約として書いてあります。中身は、スリープの章で紹介します。

スクリプトの中身はclaude -pを1回呼ぶだけ

Claude Codeは、-p(--print)を付けると対話なしで動き、終わると終了します。公式ドキュメントでは、成功なら終了コード0、失敗なら0以外を返すとされています。

下は、その呼び方を示す最小の例です。中身は架空で、僕の会社のスクリプトそのものではありません。

#!/bin/bash
# 例:毎朝の定時処理(パス・ファイル名は架空)
LOG_DIR="$HOME/Library/Logs/morning-task"
mkdir -p "$LOG_DIR"
cd "$HOME/Scripts/morning-task" || exit 1
/path/to/claude -p "$(cat prompt.md)" >> "$LOG_DIR/$(date +%F).log" 2>&1

/path/to/claude には、which claude で出た場所を書きます。指示文を別のファイルに分けると、plistを触らずに中身だけ直せます。僕の会社でも、指示文の編集に再登録は要らない作りです。

claude -p のオプションや、ほかの定期実行の方法との比べ方は、claude -pのヘッドレス実行と定期実行の実録に書きました。ここでは、OSのスケジューラから呼ぶ側に絞ります。

初回テストは約3分、受信約50件

初回のテストは、2026年5月27日の0時14分から17分まで、約3分かかりました。設定メモの記録では受信約50件、同じ日の出力の記録では60通を読み、整理した結果を社内ノートと下書きの2か所に書き出せています。

テストが通った直後の0時18分から、毎朝9時の本番の稼働に入っています。日付ごとの実行ログも、ホームのライブラリの中に残る作りです。

launchctlで登録・確認・手動実行するコマンド

launchdへの登録は bootstrap、外すのは bootout、確認は print、今すぐ動かすのは kickstart です。どれも対象を gui/ユーザーID の形で指定します。

やりたいことコマンドの例
登録するlaunchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.morning-task.plist
外す(一時停止)launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.example.morning-task.plist
状態を見るlaunchctl print gui/$(id -u)/com.example.morning-task
今すぐ動かすlaunchctl kickstart gui/$(id -u)/com.example.morning-task

$(id -u) は、今ログインしているユーザーの番号に置き換わる書き方です。macOS付属の説明では、gui/<uid> はユーザーに結びついた領域を指す、使いやすい指定だとされています。

時刻を変えたら、bootoutしてからbootstrap

僕の会社のメモでは、時刻を変えるときはplistの Hour と Minute を書き換えると決めています。そのあと bootout してから bootstrap で登録し直します。保存しただけでは変わらない前提の運用です。

一時停止したいときも、同じ bootout を使います。再開は bootstrap で、plistのファイル自体は消さずに置いておきます。

loadとstartは古い書き方

launchctl load と unload は、macOS付属の説明では古い書き方(legacy)の節にあります。代わりに bootstrap・bootout・enable・disable が勧められています。

launchctl start ラベル名 も同じ古い書き方の節にあり、デバッグや試験のための命令とされています。僕の会社の5月のメモは start で書いてありますが、今から書くなら kickstart を使います。

printの出力は、読むだけにする

launchctl print では、設定ファイルの場所、今の状態、最後の終了コードなどが見られます。動かなかったときに、まず打つ命令です。

ただしmacOS付属の説明には、この出力の形はリリースごとに予告なく変わり得るので、プログラムで読み取って頼らないよう書かれています。目で見て確かめる用途にとどめるのが安全です。

Macが眠っている間のlaunchd:スリープ中は起きてから1回、電源オフ中は動かない

StartCalendarInterval で予定した処理は、スリープ中に時刻を過ぎても、Macが起きたときに実行されます。電源が切れていた分は実行されず、次の予定時刻まで待ちます。

Appleの文書には、毎晩電源を切るMacで深夜1時に予定した処理は一度も動かない、という例まで書かれています。定期実行の時刻は、Macが確実に動いている時間帯に置くのが前提です。

3つの書き方で、取りこぼしの扱いが違う

同じ「定期実行」でも、cron、StartCalendarInterval、StartInterval で、眠っている間の扱いが変わります。Appleの文書とmacOS付属の説明から並べます。

書き方スリープ中に時刻を過ぎた電源オフ中に時刻を過ぎた
cron実行しない(次の予定時刻まで待つ)実行しない
StartCalendarInterval(時刻で指定)起きたときに実行。何回分過ぎても1回にまとめる実行しない
StartInterval(秒の間隔で指定)その回は飛ばされる実行しない

「毎朝9時」のように時刻で決めたい処理は、StartCalendarInterval が向いています。数分おきに回す処理は StartInterval で書き、何回か飛んでも困らない作りにしておきます。

macOS付属の説明では、この2つは互いを知らず、完全に別々に評価されるとも書かれています。同じplistに両方を書くと、両方の条件で動くことになります。

僕の会社のメモにある「既知の制約」

朝の定時処理のメモには、既知の制約として3点を書いています。シャットダウン中は動かないこと、9時にスリープのままなら起きた時点で1回追いかけて動くこと、電源を完全に切る運用なら起動後に手で動かす必要があることです。

この3点は、上のAppleの説明と食い違っていません。9時にスリープしていた日は、9時ちょうどではなく、起こしたときに朝の処理が走ります。

RunAtLoadは、使うかを用途で決める

RunAtLoad を true にすると、登録した時点で1回動きます。macOS付属の説明では既定は false で、起動やログインの妨げになるので避けるよう書かれています。

僕の会社の例では、10月27日に作った5分ごとの同期に RunAtLoad を付けています。登録のたびに動いてほしい処理かどうかで決めれば足ります。

Documentsの同期がlaunchdで「Operation not permitted」になった(2026年7月19日)

2026年7月19日、書類フォルダ(~/Documents)にある社内ノートの自動同期をlaunchdで回そうとして、動きませんでした。結果は Operation not permitted で、終了コードは126でした。

この日は、社内ノートをGitHubの非公開リポジトリで管理し始めた日です。スマホからも同じノートを読めるようにするため、決まった間隔で送受信する自動同期が要りました。

書類フォルダは、macOSが守っている場所

Appleのサポートページには、デスクトップ・ダウンロード・書類のフォルダは、アプリごとに読めるかどうかを決められる場所だと書かれています。設定は「システム設定」の「プライバシーとセキュリティ」の中にあります。

社内ではこの保護をTCCと呼んでいます。7月19日は、launchdから動かした同期が、この保護に止められて書類フォルダに入れませんでした。

launchdをやめて、Claude Codeのフックに切り替えた

このときはlaunchdをやめ、Claude Codeのフックで同期を動かす形に切り替えました。会話の開始で動く SessionStart と、応答の終わりに動く Stop に、同期をつないでいます。

公式ドキュメントでは、SessionStart はセッションの開始と再開のとき、Stop はClaudeが応答を終えたときに動く出来事とされています。Claude Codeを使っている間に、送受信が自動で回る形になりました。

同期そのものの手順や、2台のMacで起きた失敗は、ObsidianをGitHubで同期する方法に書いています。

フックの設定の書き方は、Claude Code hooksの使い方と実録にまとめました。launchdと違い、Claude Codeを開いていない間は同期も動きません。

終了コードが0でも、読めているとは限らない

社内のメモには、もう1つ注意を残しています。python3を直接起動して書類フォルダを読む既存のジョブは、終了コードが0でも、中では読めていない可能性があるという点です。

読めなかったときに、スクリプトの側でエラーとして終了しない作りだと、失敗が表に出ません。終了コードだけでは、成功と判断できないということです。

launchdから書類フォルダを読ませる3つの逃げ道(2026年10月の実測)

launchdを使い続けたいなら、書類フォルダに入れる実行ファイルから起動するか、読む先を書類フォルダの外に移すかのどちらかです。僕の会社の2026年10月の記録にある3つの形を並べます。

逃げ道何をするか僕の会社での結果
1. 許可済みのNode.jsから起動するplistでNode.jsを直接起動するか、Node.jsの小さな起動役から子として起動する2026年10月4日、書類フォルダを読めた
2. 実行ファイルに許可を足す「フルディスクアクセス」にその実行ファイルを追加する別のMacで必要になることがある、とメモに残している
3. 書類フォルダの外に置くスクリプトも読む先もホーム直下の別フォルダに置く2026年10月27日から、5分ごとの同期が動いている

僕なら、新しく作る処理は3から考えます。許可の状態に左右されないので、別のMacに持っていっても同じように動くからです。

10月4日の実測:bashとpython3は不可、Node.jsは可

2026年10月4日、launchdから起動して書類フォルダを読めるかを確かめました。/bin/bash と /usr/bin/python3 を直接起動すると、どちらも Operation not permitted でした。

一方、以前に許可していたNode.jsは読めました。Node.jsから子として起動したpython3とbashも、書類フォルダを読めています。

僕の会社の実測では、最初に起動した実行ファイルが許可されているかで結果が分かれたように振る舞いました。仕組みの正確な説明はAppleの公開文書で確かめられなかったので、ここでは実測の結果だけを書いておきます。

Node.jsを更新しても、許可はそのままだった

Node.jsで起動する形にすると、Node.jsを更新したときに実行ファイルの場所が変わります。社内の手順では、更新したらplistの中のパスを書き直すと決めています。

2026年10月23日には、実際にNode.jsを新しい版に上げて場所が変わりました。それでもlaunchdから書類フォルダを読めて、許可を足し直す必要はありませんでした。

別のMacではNode.jsの許可が無いこともあります。その場合は「システム設定」の「プライバシーとセキュリティ」から「フルディスクアクセス」にNode.jsを足す、という手順を社内のメモに残しています。

書類フォルダの外なら、python3を直接起動しても読めた

3つ目の形は、2026年10月27日に作った同期で使っています。2台のMacの作業メモを5分ごとに揃える処理で、plistは StartInterval が300秒、RunAtLoad を付けています。

この処理は /usr/bin/python3 を直接起動していますが、本体も読む先もホーム直下の隠しフォルダにあり、書類フォルダの外です。そのため、10月4日に読めなかったのと同じ起動のしかたでも問題なく読めました。

社内のメモには、本体を書類フォルダの中に置いて直接動かすと読めなくなるので、必ず外に置くと書いています。置き場所を決めるだけで、許可の設定を触らずに済みました。

5分間隔で、反対側に届くまで4分20秒と4分53秒

10月27日には、launchdの定期実行だけで届くかを、手動の実行なしで両方向とも測りました。片方のMacで書いてから反対側に届くまで、1回目は4分20秒、2回目は4分53秒でした。

間隔は5分なので、遅くても約5分と1回分の処理で届く計算です。途中で片方のMacの回線が一時的に落ちましたが、その間は通信が時間切れになるだけで、両方のメモに影響はありませんでした。

launchdでclaude CLIを無人で回すときの注意

launchdから動かすclaude CLIには、答える人がいません。だから、許可の確認で止まらない作りと、失敗が後から分かる記録の2つを先に用意しておきます。

公式ドキュメントでは、許可のモードを決めずに動かした対話なしの claude -p は手動モードになり、承認が要る操作は何かが承認しない限り拒否されます。

許可で止まる原因と設定は、Claude Codeで毎回許可を聞かれる原因と設定に書きました。無人で回す前に、どの操作を許すかを決めておきます。

もう1つ、終わった結果をどこで受け取るかも決めておきます。無人の処理ではスマホへの通知が届かず、メールに切り替えた経緯は、Claude Codeの通知を受け取る方法に書きました。

始める前に確かめる4つのこと

この記事で書いたことを、始める前のチェックとして並べます。

  1. plistは ~/Library/LaunchAgents に置く:自分のログイン中だけ動き、管理者権限が要らない
  2. 出力は StandardOutPath と StandardErrorPath で受ける:日付ごとのログにしておくと、どの日に失敗したかが追える
  3. スクリプトと読む先は書類フォルダの外に置く:置けないなら、許可済みの実行ファイルから起動する
  4. 時刻はMacが動いている時間に置く:電源オフ中の分は実行されない

3を外すと、7月19日の僕の会社と同じく Operation not permitted で止まります。4を外すと、エラーも出ないまま、その日の処理が抜けます。

まとめ

  • Macのlaunchdで定期実行するなら、~/Library/LaunchAgents にplistを置き、launchctl bootstrap で登録する。cronはAppleが推奨していない
  • StartCalendarInterval はスリープ中に過ぎた分を起きたときに1回実行するが、電源オフ中の分は実行しない
  • 僕の会社では2026年5月27日から、launchd、シェル、claude CLIの3段で毎朝9時の処理を回している。初回は約3分で受信約50件を整理した
  • 7月19日、書類フォルダの同期はlaunchdで Operation not permitted(終了コード126)になり、Claude Codeのフックに切り替えた
  • 僕なら最初に、スクリプトも読む先も書類フォルダの外に置いて、StartCalendarInterval で1本作るところから始めます

よくある質問

Macで定期実行するなら、cronとlaunchdのどちらを使えばいいですか?

launchdです。Appleの開発者向け文書では、cronはまだ動くものの推奨されず、launchdに置き換えられたと書かれています。スリープ中に予定時刻を過ぎた場合も、StartCalendarIntervalなら起きたときに実行されます。

launchdの定期実行は、Macの電源が切れていても動きますか?

動きません。Appleの文書では、予定の時刻に電源が切れていた分は実行されず、次の予定時刻まで待つとされています。毎晩電源を切るMacで深夜1時に予定した処理は、一度も動かないことになります。

launchctlのloadとbootstrapは何が違いますか?

どちらも設定を登録する命令ですが、macOS付属のlaunchctlの説明では、loadとunloadは古い書き方の節にあり、代わりにbootstrapとbootoutが勧められています。登録先は gui/ユーザーID のように明示します。

launchdから動かすと「Operation not permitted」になるのはなぜですか?

書類フォルダなどがmacOSのプライバシー保護の対象だからです。僕の会社の実測では、launchdから直接起動したbashやpython3は書類フォルダを読めず、許可済みのNode.jsは読めました。スクリプトと読む先を書類フォルダの外に置くと避けられます。

引用元・参考にした記事

  1. Scheduling Timed Jobs出典Apple Developer Documentation Archive参照
  2. Creating Launch Daemons and Agents出典Apple Developer Documentation Archive参照
  3. Control access to files and folders on Mac出典Apple Support参照
  4. Run Claude Code programmatically出典Claude Code Docs参照
  5. Hooks reference出典Claude Code Docs参照