Claude Codeで数時間におよぶタスクを走らせる——たとえば500ファイルの一括リファクタ、レガシーコードのTypeScript移行、大量のテスト生成——を任せると、ある確率で途中停止に遭遇します。「12個のファイルを変更し、278個処理したところで止まった。どこまで進んでいて、どこから再開すればいいのかわからない」という状況です。
ここでは同様の長時間タスクを本番運用で走らせている私の経験から、止まっても被害が最小で済み、確実に再開できる運用設計をご紹介します。扱うのは応急処置ではなく、そもそもタスク設計の段階で『止まることを前提にする』という発想の転換です。
長時間タスクが止まる4つの失敗モード
まず原因を切り分けます。Claude Codeの長時間タスクが止まるパターンは、対処が根本的に違う4種類に分類されます。
モード1: 出力トークン・コンテキスト上限
モデルが1回の応答で出せるトークン量、または会話全体で保持できるコンテキスト量に達したケースです。前者は単一応答の途中で切れ、後者はタスクの中盤以降でじわじわ品質が落ち、最終的に「何の作業中だったか忘れる」という症状になります。前者はプロンプト分割で、後者はタスク分割とセッション管理で対処します。
モード2: API側のレート制限・エラー
Anthropic APIのレート制限、一時的な503、あるいはモデル側の内部タイムアウトが返るケースです。Claude Codeはある程度自動リトライしますが、連続失敗が閾値を超えると停止します。これは外部要因なので、リトライ戦略とサーキットブレーカーで扱います。
モード3: ローカル環境の問題
ディスク容量不足、プロセスのメモリ枯渇、シェルセッションの切断、SSHトンネルのタイムアウトなど、ローカル側で起きる停止です。症状としては「Claude Codeが黙り込んだ」に見えますが、実態はローカル環境が先に力尽きています。
モード4: 作業範囲の設計ミス
実は一番多いのがこれです。「1ファイルずつ順に処理して」と頼んだつもりが、Claude Codeが依存関係解決のために何十ファイルも同時に読み込み、早期にコンテキストを使い切る。あるいは「テストも走らせて」と頼んだせいで、各ステップのテスト実行時間がボトルネックになります。タスクの粒度と範囲を設計しなおせば解決します。
本番運用で効くのは、この4つを混同せず、それぞれに別の対処を入れる設計です。
止まることを前提にした「チェックポイント駆動」設計
私が長時間タスクで必ず入れているのが、チェックポイント駆動の進行管理です。基本アイデアは単純で、タスクの進行状況を『Claude Codeの記憶』ではなく『ファイルシステム上のマニフェスト』に書き出すというものです。
最小実装のマニフェスト
作業ディレクトリの直下に、次のようなJSONを置きます。
{
"task_id": "typescript-migration-2026-04",
"total_items": 487,
"completed": [
{ "item": "src/user/profile.js", "finished_at": "2026-04-23T09:12:31Z", "status": "ok" },
{ "item": "src/user/settings.js", "finished_at": "2026-04-23T09:13:02Z", "status": "ok" }
],
"failed": [
{ "item": "src/legacy/auth.js", "reason": "ambiguous imports", "last_tried_at": "2026-04-23T09:15:48Z" }
],
"queued": [
"src/legacy/rbac.js",
"src/legacy/session.js"
]
}Claude Codeへの最初のプロンプトで、このマニフェストの仕様と更新ルールを宣言しておきます。「各ファイルの処理を完了した直後に、このJSONを更新してからコミットしてください。途中停止しても、次回起動時に queued の先頭から再開します」と明示するのです。
なぜこれが効くのか
Claude Codeが停止しても、マニフェストはファイルとして残ります。再開時に「このマニフェストを読んで、queuedの先頭から作業を再開してください」と指示するだけで、正確にどこから続きを走らせればいいかが分かります。
重要な設計上のポイントが2つあります。1つ目、各アイテムの処理が成功したら即座にマニフェストを更新すること。5ファイルまとめて更新すると、4ファイル目で停止した時に進捗が巻き戻ります。2つ目、更新をgit commitに紐付けること。万が一マニフェストが破損しても、直前のコミットから復旧できます。
実装スニペット(Bash ラッパー)
私は次のようなラッパーを使っています。Claude Codeが各アイテムの処理後に呼び出す想定です。
#!/usr/bin/env bash
# mark_done.sh — マニフェストを1件分更新してコミット
set -euo pipefail
MANIFEST="./task_manifest.json"
ITEM="$1"
STATUS="${2:-ok}"
python3 - "$MANIFEST" "$ITEM" "$STATUS" << 'PY'
import sys, json, datetime
manifest_path, item, status = sys.argv[1], sys.argv[2], sys.argv[3]
with open(manifest_path) as f:
m = json.load(f)
m["completed"].append({
"item": item,
"finished_at": datetime.datetime.utcnow().isoformat() + "Z",
"status": status
})
if item in m["queued"]:
m["queued"].remove(item)
with open(manifest_path, "w") as f:
json.dump(m, f, indent=2, ensure_ascii=False)
PY
git add "$MANIFEST"
git commit -m "progress: $ITEM ($STATUS)" --quiet各処理成功時に ./mark_done.sh src/user/profile.js を実行するだけで、進捗がコミット履歴に一行ずつ刻まれていきます。止まった後でも git log でどこまで進んだかが一目で分かります。
サーキットブレーカーの最小設計
連続失敗が続く時に、何度でもリトライしてAPIコストを溶かしてしまう事故を防ぐのがサーキットブレーカーです。ソフトウェア工学でよく使われるパターンですが、Claude Codeの運用にもそのまま使えます。
状態機械の設計
3つの状態を持たせます。
- Closed(通常運転): 失敗が閾値未満なら、リトライし続ける
- Open(回路遮断): 連続失敗が閾値を超えたら、一定時間まったく試行しない
- Half-Open(様子見): Openの冷却期間が終わったら、1件だけ試す。成功すればCloseに戻り、失敗すればOpenに戻る
実装例
import time, json, pathlib
STATE_PATH = pathlib.Path("./cb_state.json")
THRESHOLD = 3 # 連続失敗許容数
COOLDOWN_SEC = 600 # Open状態での待機秒数
def load_state():
if not STATE_PATH.exists():
return {"state": "closed", "fail_count": 0, "opened_at": 0}
return json.loads(STATE_PATH.read_text())
def save_state(s):
STATE_PATH.write_text(json.dumps(s, indent=2))
def allow_call():
s = load_state()
if s["state"] == "closed":
return True
if s["state"] == "open":
if time.time() - s["opened_at"] >= COOLDOWN_SEC:
s["state"] = "half_open"
save_state(s)
return True
return False
if s["state"] == "half_open":
return True
return False
def record_success():
save_state({"state": "closed", "fail_count": 0, "opened_at": 0})
def record_failure():
s = load_state()
s["fail_count"] += 1
if s["fail_count"] >= THRESHOLD:
s["state"] = "open"
s["opened_at"] = time.time()
save_state(s)Claude Codeから呼ぶ処理の前後で allow_call()、record_success()、record_failure() をフックするだけです。閾値3・冷却10分という設定は保守的で、一時的な503程度なら自動で回復しつつ、本格的な障害時には無駄なAPI消費を止めてくれます。
冪等リトライを前提にした作業粒度
サーキットブレーカーが働いた後の再開で事故を防ぐには、各作業が冪等(同じ操作を何度繰り返しても結果が同じ)であることが必須です。リファクタ1件の再実行で結果が壊れるなら、そもそも再開戦略が成立しません。
冪等にするための3つの設計ルール
1つ目は作業前に現在状態を確認することです。「ファイルをTypeScript化する」なら、まず拡張子とimport文から既に移行済みかを判定し、済みならスキップします。マニフェストの completed だけを信用すると、マニフェスト更新後・コミット前に停止した場合に二重処理が起きます。
2つ目は出力を一意なパスに書くことです。ログ・中間成果物は logs/{task_id}/{item}_{attempt}.log のように、試行番号まで含めた一意パスにします。同じパスを上書きすると、再実行時に前回の失敗原因を追えなくなります。
3つ目は共有リソースの変更は最後にまとめることです。DBマイグレーション、APIキーの発行、外部サービスへの通知など、冪等にしにくい操作は、全件処理が終わった後の「確定フェーズ」に集約します。途中停止時に中途半端な状態を残さない鉄則です。
部分成果を退避するプロトコル
「途中まで進んだ成果」を失わないために、停止が検知されたら即座に状態を凍結します。
停止検知のトリガー
Claude Codeの応答が20分以上来ない、あるいはサーキットブレーカーがOpen状態に入ったら、次のコマンドを自動実行するフックを仕込みます。
#!/usr/bin/env bash
# freeze.sh — 作業中の状態を退避ブランチに保存
set -euo pipefail
TASK_ID=$(python3 -c "import json; print(json.load(open('task_manifest.json'))['task_id'])")
FREEZE_BRANCH="freeze/${TASK_ID}-$(date +%Y%m%d-%H%M%S)"
git checkout -b "$FREEZE_BRANCH"
git add -A
git commit -m "freeze: ${TASK_ID} at $(date -Iseconds)" --allow-empty
git push origin "$FREEZE_BRANCH"
echo "Frozen to $FREEZE_BRANCH"退避ブランチは本線にマージせず、freeze/* として分離しておきます。再開時にマニフェストと照合して問題なければ本線へrebase、問題があれば退避ブランチの成果を捨てて、マニフェストの queued から再スタートします。
失敗通知と合わせる
停止を人間が後で気付くのでは遅すぎます。サーキットブレーカーがOpenに入った時点で、Slack・Discord・メールのいずれかに通知を飛ばすフックを入れます。最小実装は次のようにシンプルで十分です。
curl -X POST -H 'Content-Type: application/json' \
-d "{\"text\": \"⛔ CB opened on ${TASK_ID}. Check freeze/${TASK_ID}-*.\"}" \
"$SLACK_WEBHOOK_URL"タスク設計の段階で入れるべき「止まる前提」
最後に、設計の発想そのものを切り替える話をします。長時間タスクを組むとき、私は次のチェックリストを必ず通すようにしています。
- ひとつの作業単位は30秒〜5分以内に収まるか: 大きすぎる単位は、失敗時のコストが跳ね上がる
- 作業単位は冪等か、または実行前の状態チェックで冪等化できるか: できないなら粒度を変える
- 進捗はマニフェストに残るか: 残らないなら、再開戦略が存在しない
- 失敗時の通知経路はあるか: ないなら、止まったことに何時間も気付かない
- サーキットブレーカーの閾値・冷却時間は明示されているか: 暗黙値だと、暴走時の被害が予測不能
この5項目がすべて「はい」と言えるタスクだけをClaude Codeに渡す、というルールにすると、本番で止まっても慌てない運用ができるようになります。
全体を振り返ってに代えて — 最初の一歩
この記事の内容を全部いっぺんに導入するのは大変です。明日からの一歩としておすすめしたいのは、マニフェスト駆動だけを試してみることです。
JSONファイル1個と、成功時にそれを更新してコミットするBashラッパー1個。それだけで、長時間タスクの『今どこまで進んでいるか』がgit logに残り、止まっても冷静に再開できるようになります。サーキットブレーカーや退避プロトコルは、マニフェスト駆動が回り始めてから段階的に足せば十分です。
長時間タスクの失敗は避けられませんが、失敗の被害は設計で十分に抑え込めます。ぜひ、次の大きなタスクから一つずつ組み込んでみてください。