Claude Codeを定期実行する方法|headlessモード×cron完全ガイド
「毎朝ログを分析してSlackに報告」「週次でPRをまとめてチェック」——Claude Codeは人間が起動しなくても、cronやlaunchdから定期実行できます。この記事では、headlessモード(claude -p)の実践的な使い方、安全な権限設計、構造化出力でのコスト管理、そして定期実行特有のハマりどころまで、実装コード付きで解説します。
headlessモードとは:対話なしでClaude Codeを動かす
claudeコマンドに-p(--print)を付けると、対話画面を開かずに一度きりの指示を実行して結果を返すheadlessモードになります。標準入力・標準出力を扱う一般的なCLIツールと同じように、パイプやリダイレクトと組み合わせられます。
# 基本形
claude -p "このプロジェクトの認証まわりの実装を要約して"
# 標準入力を渡す
cat build-error.txt | claude -p "このビルドエラーの原因を簡潔に説明して" > output.txt
cronやCI、自作スクリプトから呼び出す用途にはこのheadlessモードを使います。
定期実行の前に:–bareモードを使う
-pだけだと、対話セッションと同じくフックやMCPサーバー、CLAUDE.mdなどのプロジェクト設定を自動で読み込みます。これは定期実行では罠になりがちです。誰かが後から追加したフックや、たまたまローカルに設定されたMCPサーバーが、cronジョブの動作を予期せず変えてしまうことがあります。
claude --bare -p "このファイルを要約して" --allowedTools "Read"
--bareを付けると、フック・スキル・プラグイン・MCPサーバー・自動メモリ・CLAUDE.mdの自動読み込みをすべてスキップし、渡したフラグだけで動く再現性の高い実行になります。公式ドキュメントも「CI・スクリプト用途には--bareを推奨」としており、将来的には-pのデフォルト挙動になる予定です。
--bareはOAuth・キーチェーンの読み込みもスキップするため、Anthropic APIを使う場合はANTHROPIC_API_KEY環境変数(または--settingsに渡すJSON内のapiKeyHelper)での認証が必須になります。cron環境ではそもそも対話ログインができないため、この点はむしろ好都合です。
権限設計:定期実行では dontAsk が基本
cronジョブは誰も画面を見ていない状態で動くため、確認プロンプトが出ても誰も答えられません。権限モードはdontAskを基本にするのが安全です(他のモードとの違いは権限モード完全ガイドで解説しています)。
claude --bare -p "テストを実行して失敗があれば修正して" \
--permission-mode dontAsk \
--allowedTools "Bash(npm test),Read,Edit"
dontAskモードでは、--allowedToolsやpermissions.allowルールに一致しない操作は確認ではなく自動拒否されます。セッションが入力待ちで止まったまま次のcron実行とかち合う、という事故を防げます。
「確認で止まるのが面倒だから」とbypassPermissions(--dangerously-skip-permissions)をcronに組み込むのは避けてください。安全性判定が一切ない状態で無人実行させることになり、意図しない操作が誰にも気づかれないまま実行され続けるリスクがあります。dontAsk+明示的な許可ルールの組み合わせで、必要な操作だけを狭く許可するのが正解です。
実践:ログを分析してSlackに日次レポート
構造化出力(--output-format json)を使うと、結果をスクリプトで扱いやすくなり、コストの追跡も同時にできます。
#!/usr/bin/env bash
set -euo pipefail
RESULT=$(claude --bare -p "今日のアプリケーションログ(/var/log/app.log)を分析し、
エラー件数・主な原因・対応が必要な項目を3行以内で要約して" \
--allowedTools "Read" \
--output-format json)
SUMMARY=$(echo "$RESULT" | jq -r '.result')
COST=$(echo "$RESULT" | jq -r '.total_cost_usd')
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"📋 今日のログサマリー\n${SUMMARY}\n(cost: \$${COST})\"}" \
"$SLACK_WEBHOOK_URL"
--output-format jsonのレスポンスにはresult(本文)に加えてtotal_cost_usd(実行コスト)が含まれるため、実行のたびにコストをログへ残す運用が簡単にできます。日々の定期実行が積み重なるとコストも積み重なるので、この可視化は早い段階でやっておくべきです。
JSON Schemaで出力形式を固定する
Slack投稿やDB保存など、後続処理が特定のフィールドを期待する場合は--json-schemaで出力を強制できます。
claude --bare -p "今日のエラーログから、エラーメッセージの配列を抽出して" \
--allowedTools "Read" \
--output-format json \
--json-schema '{"type":"object","properties":{"errors":{"type":"array","items":{"type":"string"}}},"required":["errors"]}' \
| jq '.structured_output'
cron・launchdへの登録
Linux・Mac共通:crontab
# crontab -e で編集
# 毎朝9時に実行し、ログをファイルに残す
0 9 * * * /usr/local/bin/claude-daily-report.sh >> /var/log/claude-cron.log 2>&1
cronはログインシェルの.zshrc・.bashrcを読み込まないため、ANTHROPIC_API_KEYやPATHがスクリプト内で見えないことがあります。スクリプトの先頭で明示的にexport ANTHROPIC_API_KEY=...するか、cron専用の環境変数ファイルをsourceしてから実行してください。
Macでより確実に動かす:launchd
Macでは、スリープ復帰後の実行漏れに強いlaunchdもおすすめです。
<!-- ~/Library/LaunchAgents/com.example.claude-daily.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.claude-daily</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/claude-daily-report.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key><integer>9</integer>
<key>Minute</key><integer>0</integer>
</dict>
</dict>
</plist>
launchctl load ~/Library/LaunchAgents/com.example.claude-daily.plist
Macを定期実行のホストにする場合、スリープしていると当然ジョブも実行されません。蓋を閉じたまま動かし続ける設定はこちらのスリープ対策ガイドを参照してください。
エラーハンドリングとタイムアウト
無人実行では、失敗を検知して次のアクションにつなげる仕組みが欠かせません。
| 状況 | 挙動・対処 |
|---|---|
| 処理が正常終了 | 終了コード0。$?で判定できる |
SIGTERMで停止(killやタイムアウト超過) | 実行中のBashプロセスツリーを終了し、SessionEndフックを実行してから終了コード143で終了 |
| 標準入力が10MB超 | 明確なエラーを出して非ゼロ終了。大きなデータはファイル参照に切り替える |
| バックグラウンドタスクの放置 | 結果返却後5秒の猶予でシェルを終了(サブエージェント・ワークフローは最大10分待機) |
# タイムアウト付き実行 + 終了コードでの分岐
if ! timeout 300 claude --bare -p "$PROMPT" --allowedTools "Read,Bash(npm test)" > result.json; then
echo "Claude Codeの実行が失敗またはタイムアウトしました" | \
curl -X POST -d @- "$SLACK_WEBHOOK_URL"
exit 1
fi
定期実行は「誰も見ていない場所で動く」からこそ、実行結果・コスト・エラーをすべてログファイルに残す習慣が重要です。--output-format jsonで得られるsession_idを記録しておけば、あとから--resumeで当時のセッションを辿って調査することもできます。
会話を継続する定期実行(差分レポートなど)
「昨日の続きから今日の分だけ確認する」といった継続的なタスクには、--continueや--resumeが使えます。
# セッションIDを記録しながら実行
SESSION_ID=$(claude -p "今週のPRレビューを開始" --output-format json | jq -r '.session_id')
echo "$SESSION_ID" > /var/tmp/claude-weekly-session.txt
# 翌日、同じセッションを継続
claude -p "昨日の続きから、新しく増えたPRだけレビューして" \
--resume "$(cat /var/tmp/claude-weekly-session.txt)"
セッションIDの検索範囲はカレントディレクトリ(とそのgit worktree)に紐づくため、cronジョブは常に同じディレクトリから実行するようにしてください。
よくある質問
Q. GitHub Actionsのスケジュール実行と何が違いますか?
GitHub Actionsのon: scheduleはGitHubのインフラ上で動くため、GitHub Actionsの実行時間を消費します。一方、自前のcron・launchdは自分のサーバーやMacで動かすため、GitHub Actions分数を消費しません。GitHubリポジトリに閉じた作業ならActions、ローカルファイルやリポジトリ外のシステムも触るなら自前cronが向いています。GitHub Actions側の実践例はGitHub Issue・PR自動化の記事で解説しています。
Q. 複数のタスクを並行して定期実行しても大丈夫ですか?
技術的には可能ですが、同じディレクトリに対して複数のcronジョブが同時に書き込みを行うと競合するリスクがあります。タスクごとに作業ディレクトリを分けるか、ロックファイルで排他制御することをおすすめします。
Q. コストが想定より高くなっていないか、どう監視すればいいですか?
--output-format jsonのtotal_cost_usdを毎回ログに残し、日次・週次で合計を集計する仕組みを作るのが確実です。想定を超えたら通知するアラートをスクリプト側に組み込んでおくと安心です。
Q. –bareを付けると自分のカスタムコマンドやCLAUDE.mdが使えなくなりますか?
そのとおりです。必要なコンテキストは明示的に渡します。システムプロンプトの追加は--append-system-prompt、設定は--settings、MCPサーバーは--mcp-configで個別に指定できます。定期実行では「暗黙的に読み込まれるものを減らし、明示したものだけで動かす」方が事故が起きにくいです。
まとめ
claude -p(headlessモード)でcron・launchdからの無人実行ができる- 定期実行では
--bareで暗黙の設定読み込みを止め、再現性の高い実行にする - 権限は
dontAsk+明示的な許可ルールが基本。bypassPermissionsは使わない --output-format jsonで結果とコスト(total_cost_usd)を同時に取得し、ログとして残す- タイムアウト・終了コード(143など)を検知して通知する仕組みを組み込む
まずは1つ、リスクの低い「要約して通知するだけ」のジョブから試してみてください。--bareとdontAskを基本形にしておけば、範囲が広がっても安全に運用しやすくなります。


