AIエージェントをバックグラウンド実行する — cron · launchd · タスクスケジューラ
Claude CodeなどのAIエージェントを定期的に無人実行するスケジューリングパターン。macOS launchd · Linux cron · Windowsタスクスケジューラを同一シナリオで比較。
Claude Codeのようなエージェントは、キーボードの前にいる時だけのものではない。夜間PR レビュー、Issue トリアージ、README 同期、ログ要約 — 無人実行することで価値が積み重なる。以前はエージェントを対話型ツールとしてしか使っていなかった。いまでは定期実行こそがエージェントの真価だと考える。単発の会話ではなく、継続的な自動化にあるからだ。
このガイドでは、3つのOS(macOS launchd、Linux cron、Windows タスクスケジューラ)の標準スケジューリングツールを同一シナリオで比較し、AIエージェント特有の落とし穴(APIキーの安全管理、ログ取得、失敗通知)を解説する。
TL;DR
| OS | ツール | 定義場所 | ログ |
|---|---|---|---|
| macOS | launchd (LaunchAgent) | ~/Library/LaunchAgents/<label>.plist | StandardOutPath/StandardErrorPath |
| Linux | cron | crontab -e または /etc/cron.d/<name> | MTA またはリダイレクト >> log.txt 2>&1 |
| Windows | タスクスケジューラ | GUI または PowerShell Register-ScheduledTask | イベントビューアまたはファイルリダイレクト |
5つのよくある落とし穴:
- PATHが空 — コマンドが見つからない
- APIキーを plist/crontab にプレーンテキストで保存(禁止)
- 同一ジョブの二重起動による競合
- サイレントな失敗 — 通知なし
- マシンがスリープ中 — ジョブがスキップされる
§1〜§3はOS別のパターン、§4は落とし穴について説明する。
前提条件
- 実行するAIエージェント(例: Claude Code、Cursor CLI、カスタムスクリプト)
- 環境変数またはシークレットマネージャーで管理されたAPIキー
- シェルスクリプト(例:
~/agents/daily-pr-review.sh)
1. macOS — launchd LaunchAgent — 10分
macOSにもcronはありますが、Appleはlaunchdを推奨している。スリープ後・再ログイン後のキャッチアップ実行もより確実だ。
1.1 plistを作成する
~/Library/LaunchAgents/dev.local.daily-pr-review.plist:
<?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>dev.local.daily-pr-review</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-lc</string>
<string>$HOME/agents/daily-pr-review.sh</string>
</array>
<!-- 環境変数: PATHが空になることがあるため明示的に設定 -->
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
<!-- ANTHROPIC_API_KEYをここに書かないこと — §4.2参照 -->
</dict>
<!-- 毎日09:00 -->
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key><integer>9</integer>
<key>Minute</key><integer>0</integer>
</dict>
<!-- スリープ後のキャッチアップ -->
<key>RunAtLoad</key><false/>
<key>StartOnMount</key><false/>
<!-- ログ -->
<key>StandardOutPath</key>
<string>/tmp/daily-pr-review.out.log</string>
<key>StandardErrorPath</key>
<string>/tmp/daily-pr-review.err.log</string>
</dict>
</plist>1.2 登録と実行
# 登録
launchctl load ~/Library/LaunchAgents/dev.local.daily-pr-review.plist
# 即時実行(スケジュールをスキップ)
launchctl start dev.local.daily-pr-review
# ステータス確認
launchctl list | grep daily-pr-review
# 登録解除
launchctl unload ~/Library/LaunchAgents/dev.local.daily-pr-review.plist1.3 スリープ後のキャッチアップ
cronはスリープ中のジョブをスキップする。launchdはウェイク後に1回実行します(注: StartCalendarInterval のキャッチアップは1回のみです)。
毎時などのキャッチアップには:
<key>StartInterval</key>
<integer>3600</integer> <!-- 1時間ごと -->→ cronと違い、ウェイク後に1回実行される。
2. Linux — cron — 5分
2.1 crontabを編集する
crontab -e例:
# m h dom mon dow command
0 9 * * * /home/user/agents/daily-pr-review.sh >> /home/user/logs/pr-review.log 2>&1| フィールド | 値 | 意味 |
|---|---|---|
0 | 0 | 毎時0分 |
9 | 9 | 午前9時 |
* * * | every | 毎日 |
毎日09:00に実行し、stdout/stderrをログファイルに追記する。
2.2 PATHを明示的に設定する
cronのPATHは非常に限られています。スクリプト内またはcrontabの先頭で設定してください:
PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/sbin
SHELL=/bin/bash
0 9 * * * $HOME/agents/daily-pr-review.sh >> $HOME/logs/pr-review.log 2>&12.3 システムcron(/etc/cron.d/)
サーバーにルートレベルで登録する場合:
sudo tee /etc/cron.d/daily-pr-review > /dev/null <<'EOF'
PATH=/usr/local/bin:/usr/bin:/bin
SHELL=/bin/bash
0 9 * * * appuser /home/appuser/agents/daily-pr-review.sh >> /var/log/agent.log 2>&1
EOFappuserフィールドで実行ユーザーを指定する。セキュリティ上重要だ。
2.4 systemd タイマー(現代的な代替手段)
cronの代替手段。ユニットファイルで再起動・障害処理・ログを一元管理できる。
# /etc/systemd/system/pr-review.service
[Unit]
Description=Daily PR review agent
[Service]
Type=oneshot
User=appuser
ExecStart=/home/appuser/agents/daily-pr-review.sh
StandardOutput=journal
StandardError=journal# /etc/systemd/system/pr-review.timer
[Unit]
Description=Run daily PR review at 09:00
[Timer]
OnCalendar=*-*-* 09:00:00
Persistent=true # ダウンタイム中に時刻が過ぎた場合、起動後に実行
[Install]
WantedBy=timers.targetsudo systemctl enable --now pr-review.timer
journalctl -u pr-review.service # ログPersistent=trueはcronに対する大きな利点 — サーバーダウン後の自動キャッチアップが可能だ。
3. Windows — タスクスケジューラ — 10分
GUIでも設定できますが、PowerShellで再現性を持たせましょう。
3.1 PowerShellで登録する
# アクション
$action = New-ScheduledTaskAction `
-Execute "wsl.exe" `
-Argument "-d Ubuntu -e bash -lc '~/agents/daily-pr-review.sh'"
# トリガー: 毎日09:00
$trigger = New-ScheduledTaskTrigger -Daily -At 9am
# 設定: ユーザーがログインしていなくても実行; スリープから復帰して実行
$settings = New-ScheduledTaskSettingsSet `
-StartWhenAvailable `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-ExecutionTimeLimit (New-TimeSpan -Hours 1) `
-RestartCount 2 `
-RestartInterval (New-TimeSpan -Minutes 10)
# 登録
Register-ScheduledTask `
-TaskName "DailyPRReview" `
-Action $action `
-Trigger $trigger `
-Settings $settings `
-Description "Run Claude Code daily PR review in WSL"3.2 GUIで確認する
Win + R → taskschd.msc → タスクスケジューラライブラリ → 「DailyPRReview」
3.3 即時テスト実行
Start-ScheduledTask -TaskName "DailyPRReview"
Get-ScheduledTaskInfo -TaskName "DailyPRReview" | Select LastRunTime, LastTaskResultLastTaskResult: 0は成功を意味する。
3.4 WSLへの呼び出し
上記のように wsl.exe -d Ubuntu -e bash -lc '...' を使用する。環境変数とAPIキーはWSLの ~/.bashrc または別の環境ファイルに保存する。
3.5 ログの取得
タスクスケジューラ自体のログは簡素だ。スクリプト内でリダイレクトしてください:
# ~/agents/daily-pr-review.sh
#!/usr/bin/env bash
set -euo pipefail
LOG="$HOME/logs/pr-review-$(date +%Y%m%d).log"
mkdir -p "$(dirname "$LOG")"
exec >> "$LOG" 2>&1
echo "[$(date)] starting..."
# エージェントを実行
claude-code --resume --task pr-review --quiet
echo "[$(date)] done."4. AIエージェント特有の落とし穴
4.1 PATHが空 — 「動かない」原因の第1位
インタラクティブシェルのPATH ≠ launchd/cronのPATH。スクリプトの先頭に明記してください:
#!/usr/bin/env bash
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:$PATH"またはplist/crontabの EnvironmentVariables / PATH= ヘッダーで設定する。
確認方法:
launchctl getenv PATH # macOS
sudo -u appuser env | grep PATH # Linux cronユーザー4.2 APIキーをplist/crontabに絶対書かない
plistの EnvironmentVariables ブロックに ANTHROPIC_API_KEY=sk-ant-... と書いた場合:
ls -laで露出する- Time Machine / iCloudバックアップにプレーンテキストで保存される
- 同マシン上の他ユーザーが
cat ~/Library/LaunchAgents/*.plistで読み取れる
代替手段:
- macOS: Keychain + スクリプトから
security find-generic-password -s anthropic-api -wで読み取る - Linux: 別途
~/.config/agent/envファイルをchmod 600で作成し、スクリプトでsourceする - Windows:
Get-Credentialまたは資格情報マネージャー
スクリプト(macOS):
#!/usr/bin/env bash
set -euo pipefail
export ANTHROPIC_API_KEY="$(security find-generic-password -s anthropic-api -w 2>/dev/null)"
[ -z "$ANTHROPIC_API_KEY" ] && { echo "missing API key in Keychain"; exit 1; }
# ... エージェントを実行キーを1回登録する:
security add-generic-password -s anthropic-api -a "$USER" -w 'sk-ant-...'4.3 二重起動を防ぐ — flock / シングルインスタンス
二重起動 = 2倍のAPIコスト + 競合。flockでロックする:
#!/usr/bin/env bash
exec 200>/tmp/daily-pr-review.lock
flock -n 200 || { echo "already running"; exit 0; }
# ... エージェントを実行flock -n = ノンブロッキング; ロック中は即座に終了する。
4.4 サイレントな失敗を防ぐ — 失敗通知
デフォルトでは、stderrはログファイルに書き込まれるだけだ。trapで通知しましょう:
#!/usr/bin/env bash
set -euo pipefail
notify_failure() {
local code=$?
if [ $code -ne 0 ]; then
# macOS — terminal-notifier または osascript
osascript -e "display notification \"PR review failed (exit $code)\" with title \"Agent\""
# または Slack webhook / メール / Sentry
# curl -X POST -H 'Content-Type: application/json' \
# -d "{\"text\":\"Agent failed: exit $code\"}" "$SLACK_WEBHOOK_URL"
fi
}
trap notify_failure EXIT
# エージェントを実行4.5 スリープ・起動後のキャッチアップ
- macOS launchd: スリープからウェイク後に
StartCalendarIntervalで1回キャッチアップ - Linux systemd タイマー:
Persistent=trueを使用 - Windows: タスクスケジューラの 「スケジュールされた開始時刻を過ぎた場合、できるだけ早くタスクを開始する」 をONにする
cronだけはスリープ後のキャッチアップが不可能 — それがノートPCでcronを使わない理由だ。
5. 実践シナリオ — Claude Code 毎日PRレビュー
~/agents/daily-pr-review.sh:
#!/usr/bin/env bash
set -euo pipefail
# 1. PATH + シークレット
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
export ANTHROPIC_API_KEY="$(security find-generic-password -s anthropic-api -w)"
export GITHUB_TOKEN="$(security find-generic-password -s github-token -w)"
# 2. ロック
exec 200>/tmp/daily-pr-review.lock
flock -n 200 || exit 0
# 3. ログ
LOG="$HOME/logs/pr-review-$(date +%Y%m%d).log"
mkdir -p "$(dirname "$LOG")"
exec >> "$LOG" 2>&1
echo "[$(date)] starting daily PR review..."
# 4. 作業
cd "$HOME/code/my-repo"
git fetch --quiet origin
gh pr list --state open --json number,title,author --jq '.[] | select(.author.login != "me")' | \
while read -r pr; do
num=$(echo "$pr" | jq -r .number)
echo "Reviewing PR #$num..."
claude-code --task "Review PR #$num — focus on security, performance, missing tests" --quiet
done
echo "[$(date)] done."LaunchAgentとして登録すれば、毎日09:00に自動実行される。
6. トラブルシューティング
launchd 「Service exited with abnormal code: 127」
PATHが空またはスクリプトに実行権限がありません。chmod +x ~/agents/*.sh + plist内でPATHを設定してください。
cronが実行されずログもない
- crontabの構文エラー:
crontab -lで確認 - MTA(mailutils)がない場合、stderrが消える → 明示的に
2>&1 >> log journalctl -u cron(またはcronie)で実行履歴を確認
タスクスケジューラ「Last Task Result: 0x1」
スクリプトが終了コード1で終了している。WSL内で直接実行してデバッグしてください。$LASTEXITCODEを確認する。
claude-code コマンドが見つからない
スクリプトのPATHに ~/.npm-global/bin またはnpmグローバルbinが含まれていません。which claude-code のディレクトリをPATHに追加してください。
次のステップ
- Claude Code セットアップ — 前提条件
- Claude Code hooks — 自動化イベントパターン
- マルチエージェントワークフロー — マルチエージェント連携
参考リンク
変更履歴
- 2026-05-12 — 初版(devAlice M2 シード展開)