Claude Code CLIのcron定期実行を設定する手順

Claude Code CLIのcron定期実行を設定する手順

※本記事にはプロモーション(広告・アフィリエイトリンク)を含みます。

Claude Code CLIのclaude -pをPythonのsubprocessから呼び出し、cronで定期実行する構成を組んでいる。APIキーを別途管理することなくコンテンツ生成を自動化できるこの組み合わせは、定型的な記事生成タスクと相性がいい。環境変数やvenvまわりのつまずきを含めて、再現できる形でまとめておく。

claude -p の非対話モード

通常のclaudeコマンドはREPLを起動し、対話形式で進める。-p(print)オプションをつけると、引数のプロンプトに対して1回だけ推論を走らせ、結果を標準出力に書き出して終了する。

# 対話モード(REPLが起動する)
claude

# 非対話モード(1回で終了)
claude -p '次のトピックで記事の見出し案を3つ出してください: cron自動化'

CLIの動作モードを比較すると以下になる。

オプション 動作 用途
なし REPLが起動 対話的な作業
-p "プロンプト" 1回推論して終了 スクリプト・cron組み込み

APIキーが不要な点が重要だ。Anthropic APIとは別に、Claude Codeのサブスクリプション認証で動く(執筆時点)。APIキーの発行・管理・ローテーションが不要で、claudeにログイン済みであればそのまま使える。

Python の subprocess で呼び出す

実際に使っている呼び出しの基本形は以下だ。

import subprocess
import os
from pathlib import Path

def run_claude(prompt: str, timeout: int = 300) -> str:
    env = {
        **os.environ,
        'HOME': str(Path.home()),
    }
    result = subprocess.run(
        ['claude', '-p', prompt],
        capture_output=True,
        text=True,
        timeout=timeout,
        env=env,
    )
    if result.returncode != 0:
        raise RuntimeError(
            f'claude exited with code {result.returncode}: {result.stderr}'
        )
    return result.stdout.strip()

HOMEを明示しているのは後述するcron実行時の認証エラー対策だ。cron環境ではHOMEが未設定になるケースがあり、それを事前に防ぐ。

複数のトピックをまとめて処理するバッチ版は以下のようになる。

import time
import sys

def generate_batch(topics: list[str], delay: float = 10.0) -> dict[str, str]:
    results = {}
    for i, topic in enumerate(topics):
        print(f'[{i+1}/{len(topics)}] {topic}')
        try:
            results[topic] = run_claude(f'次のトピックで記事を書いてください: {topic}')
            if i < len(topics) - 1:
                time.sleep(delay)
        except Exception as e:
            print(f'ERROR: {topic}: {e}', file=sys.stderr)
    return results

delayを設けているのは連続リクエストへの保険だ。具体的に何秒以上空けなければならないかは執筆時点で公式に明示されていないため、余裕を見て設定している。

timeoutの目安:短いコンテンツなら60〜120秒、2000文字以上の記事本文なら300〜600秒。実際の所要時間はログを眺めながら調整する。環境によって異なる。

cron に乗せる

crontab -eで設定する。手順を最小にするなら以下の記述で動く。

PATH=/usr/local/bin:/usr/bin:/bin:/home/kxkxk/.local/bin
HOME=/home/kxkxk

30 6 * * * /home/kxkxk/auto-media/venv/bin/python /home/kxkxk/auto-media/generate.py >> /home/kxkxk/auto-media/logs/cron.log 2>&1

PATH を明示する理由

cronのシェルはPATH=/usr/bin:/binしか持っていない。claudeコマンドは通常~/.local/bin/や/usr/local/bin/にインストールされるため、そのままではcommand not foundになる。

# まずclaudeの場所を確認する
which claude
# 例: /home/kxkxk/.local/bin/claude

which claudeで確認したディレクトリをcrontabのPATHの末尾に追加する。

venv の Python をフルパスで指定する

cronは.bashrcや.profileを読まないのでsource venv/bin/activateが効かない。Pythonインタープリタをフルパスで指定する。

# NG: cronではactivateが機能しない
source /home/kxkxk/auto-media/venv/bin/activate && python generate.py

# OK: フルパスで指定する
/home/kxkxk/auto-media/venv/bin/python /home/kxkxk/auto-media/generate.py

よくあるエラーと対処

認証エラーが出る場合

Claude Codeの認証情報は~/.claude/以下に保存されている。cron実行時にHOMEが未設定だと認証ファイルが見つからずエラーになる。crontabの先頭にHOME=/home/<ユーザー名>を追加すること。前節のsubprocess呼び出しでenvにHOMEを明示している場合も、念のためcrontabにも書いておく方が確実だ。

プロセスが終了しない場合

timeoutを指定しないと、Claudeが何らかの理由で応答しない場合にプロセスが無限待機する。cronジョブとして動かすなら必ず設定する。タイムアウト時はsubprocess.TimeoutExpiredが送出されるので捕捉する。

import subprocess
import sys

try:
    result = run_claude(prompt, timeout=300)
except subprocess.TimeoutExpired:
    print('タイムアウト: 処理を中断します', file=sys.stderr)
    sys.exit(1)

ログで状況を把握する

cronの実行結果はターミナルに表示されない。必ずログに記録する。

import datetime
import sys
import traceback

def main():
    ts = datetime.datetime.now().isoformat(timespec='seconds')
    try:
        print(f'[{ts}] 開始')
        result = run_claude(prompt)
        save_output(result)
        print(f'[{ts}] 完了')
    except Exception as e:
        print(f'[{ts}] ERROR: {e}', file=sys.stderr)
        traceback.print_exc(file=sys.stderr)
        sys.exit(1)

if __name__ == '__main__':
    main()

sys.exit(1)で終了コードを非ゼロにしておくと、MTAが設定されていればcronがエラーをメール通知する。crontabの末尾の2>&1で標準エラーもログファイルに書き出しておくと後から追跡しやすい。

生成物は一旦ファイルに保存する

Claudeの出力をそのままデータベースに書き込むより、ファイルに保存してから確認→取り込む2段構成が安全だ。

import datetime
from pathlib import Path

def save_output(content: str, prefix: str = 'article') -> Path:
    output_dir = Path('output')
    output_dir.mkdir(exist_ok=True)
    filename = f'{datetime.date.today()}-{prefix}.md'
    output_path = output_dir / filename
    output_path.write_text(content, encoding='utf-8')
    return output_path

現在の構成では「生成→ファイル保存」まで自動で、「確認→投稿」は手動で行っている。完全無人化で品質を保つのは難しく、この分担が現実的だと感じている。

まとめ

claude -pをPythonのsubprocessで包んでcronに乗せれば、APIキー不要でコンテンツ生成を定期自動化できる。まず手元でwhich claudeを実行してコマンドの場所を確認し、claude -p 'テスト'が応答を返してくることを確かめてから、crontabに1行追加してスモールスタートするのが確実だ。cronまわりのつまずきの大半はPATHとHOMEの環境変数の問題なので、crontabの先頭に2行明示するだけで大部分が解消する。

よくある質問

Claude Code CLIをAPIキーなしでスクリプトから自動化できますか?

できます。`claude -p`はClaude Codeのサブスクリプション認証を使うため、Anthropic APIキーは不要です。`claude`コマンドにログイン済みであれば、Pythonのsubprocessやcronからそのまま呼び出せます(執筆時点)。

cronからclaudeコマンドが見つからないと言われます

cronのPATHには通常`~/.local/bin`が含まれていません。crontabの先頭に`PATH=/usr/local/bin:/usr/bin:/bin:/home/<ユーザー名>/.local/bin`を追加し、`which claude`で確認したパスが含まれているか確かめてください。

cronでClaude Codeが認証エラーになります

Claude Codeは`~/.claude/`に認証情報を保存します。cron実行時に`HOME`が未設定だと認証ファイルが見つかりません。crontabの先頭に`HOME=/home/<ユーザー名>`を追加してください。