Tips & Tricks

Claude Code で起きた本番障害7事例と完全復旧手順|RCA・再発防止策付き

Claude Code の本番運用で実際に起きた障害を7件公開。APIキー漏洩・DB消失・課金爆発・サービスダウンの原因・復旧手順・RCA・再発防止策を完全解説。

「Claude Code は便利だが、本番で使うのは怖い」——この感覚を持つエンジニアは多い。その直感は正しいです。

Claude Code は IDE より高い権限でファイルシステムとシェルを操作します。同時に、承認ボタンをクリックし続けることで警戒心が薄れるという人間心理の弱点もあります。この2つが組み合わさった時、本番障害が起きます。

この記事では、実際に Claude Code が絡んで起きた本番障害7事例を、原因・被害範囲・復旧手順・RCA(根本原因分析)・再発防止策とともに公開します。自社での事故前に読んでください。


事例1: APIキー漏洩 → 不正利用で $3,000 課金

タイムライン

09:12  Claude Code に「CI に環境変数を渡すため .env をコミットして」と指示
09:13  git add .env && git push が承認なしで実行 (allow リストが緩すぎた)
09:14  GitHub がシークレットスキャンで検出、メール通知
09:31  AWS クローラーが OpenAI キーを検出、不正利用開始
11:00  OpenAI ダッシュボードで $3,000 の課金を確認

復旧手順

# Step 1: 即座にAPIキーを無効化 (最優先・5分以内)
# → OpenAI/各サービスのダッシュボードでキーをrevoke

# Step 2: git 履歴から .env を完全削除
git filter-branch --force --index-filter \
  "git rm --cached --ignore-unmatch .env" \
  --prune-empty --tag-name-filter cat -- --all

# Step 3: 全ブランチをforce push
git push origin --force --all
git push origin --force --tags

# Step 4: .gitignore に追加して再発防止
echo ".env" >> .gitignore
git add .gitignore && git commit -m "security: add .env to gitignore"

# Step 5: 新しいAPIキーを発行して .env に設定

RCA

  • 直接原因: settings.jsonallowBash(git add*) が入っており、確認なしで実行された
  • 根本原因: セキュリティ設定を後回しにしてプロダクトコードを優先した

再発防止

// .claude/settings.json
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash(git add*)",
      "hooks": [{
        "type": "command",
        "command": "git diff --cached --name-only | grep -E '\\.env' && echo '🚨 .env 検出!中止' && exit 1 || exit 0"
      }]
    }]
  }
}

事例2: rm -rf でプロジェクト全体が消滅

タイムライン

14:33  「node_modules をクリーンアップして再インストールして」と指示
14:33  Claude Code が rm -rf node_modules を実行 (ここまでは正常)
14:34  続けて「古いビルドファイルも消して」の指示で rm -rf dist/ を実行
14:34  パスの解釈ミスで rm -rf dist /src が実行された (スペース区切り)
14:35  src/ ディレクトリ丸ごと消滅。git 管理外の設定ファイルも消える
14:40  git checkout . で管理対象は復元できたが、.env, ローカル設定は永久消失

復旧手順

# git 管理対象は復元可能
git checkout .
git clean -fd   # 余計なファイルは削除

# 消えたファイルを git stash や reflog から探す
git stash list
git reflog

# git 管理外 (.env 等) はバックアップから復元
# → バックアップがない場合は1から再設定

RCA

  • 直接原因: スペース含むパスを適切にクォートせず rm -rf dist /src になった
  • 根本原因: rm -rfask ではなく allow に入っていた

再発防止

{
  "permissions": {
    "deny": ["Bash(rm -rf ~*)", "Bash(rm -rf /*)"],
    "ask": ["Bash(rm*)"]
  },
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash(rm*)",
      "hooks": [{
        "type": "command",
        "command": "echo '⚠️ 削除コマンドを検出。対象: $CLAUDE_TOOL_INPUT_COMMAND\n5秒後に実行。Ctrl+C で中止。' && sleep 5"
      }]
    }]
  }
}

事例3: git push --force で同僚3人分のコミットが消滅

タイムライン

16:00  「リモートと競合してる。ローカルを優先して上書きして」と指示
16:01  git push --force origin main を実行
16:01  同僚3名がその日コミットした約200行のコードが消滅
16:10  同僚Aが「あれ、自分のコミットが消えてる」とSlack投稿
16:15  原因判明。同僚A/BはローカルPCに残っていたが、Cさんは削除済みで完全消失

復旧手順

# reflog で消えたコミットを探す (実行した本人のマシンで)
git reflog | head -30
# → 例: abc1234 HEAD@{3}: push --force 前のコミット

# 消えたコミットを復元
git checkout -b recovery abc1234
git push origin recovery

# main にマージして整理
git checkout main
git merge recovery --no-ff
git push origin main

RCA

  • 直接原因: 競合解消の意図が --force-with-lease ではなく --force で解釈された
  • 根本原因: main ブランチへの force push が deny に入っていなかった

再発防止

{
  "permissions": {
    "deny": [
      "Bash(git push --force *main*)",
      "Bash(git push --force *master*)",
      "Bash(git push -f *main*)"
    ]
  }
}
<!-- CLAUDE.md -->
## git ルール
- `git push --force` は禁止
- 競合解消には `git push --force-with-lease` を使う
- main/master への push は必ずユーザー確認後

事例4: DB マイグレーション失敗で本番データ4万件が消滅

タイムライン

10:00  「users テーブルに phone_number カラムを追加するマイグレーションを実行して」
10:01  Claude Code がマイグレーションスクリプトを生成・実行
10:01  スクリプトのバグで DROP COLUMN を含む逆マイグレーションが実行される
10:02  本番の users.email カラム (NOT NULL) が消滅
10:02  全 API が 500 エラー開始、サービス完全停止
10:05  障害を認識、原因調査開始
10:30  前日のスナップショットから復元 (1日分のデータ消失)

復旧手順

# 1. 即座にサービスをメンテナンスモードに
# nginx: return 503;  or  Vercel: maintenance page

# 2. DB の現状確認
psql $DATABASE_URL -c "\d users"

# 3. バックアップから復元 (RDS の場合)
aws rds restore-db-instance-to-point-in-time \
  --source-db-instance-identifier mydb \
  --target-db-instance-identifier mydb-restored \
  --restore-time 2026-04-17T23:00:00Z

# 4. カラムが残っている場合は手動で追加
ALTER TABLE users ADD COLUMN email VARCHAR(255);
UPDATE users SET email = '(要復旧)' WHERE email IS NULL;

# 5. サービス再開

RCA

  • 直接原因: マイグレーションスクリプトの生成時に up/down を混同した
  • 根本原因: ステージング環境でのテストなしに本番実行した

再発防止

<!-- CLAUDE.md -->
## DB マイグレーション必須ルール
1. ステージング環境で必ず動作確認してから本番へ
2. マイグレーション前に必ず手動バックアップ:
   pg_dump $DATABASE_URL > backup_$(date +%Y%m%d_%H%M%S).sql
3. DROP COLUMN / TRUNCATE / DELETE (WHERE なし) を含むスクリプトは
   必ずユーザーに確認を取ってから実行
4. 本番 DATABASE_URL の場合、実行前に 'PRODUCTION DB への書き込みです。続けますか?' を表示

事例5: 無限 API コールで一夜で $800 の課金

タイムライン

23:00  「エラーが出た場合は自動でリトライして」と指示してバッチ処理を開始
23:01  外部 API が 503 を返し始める
23:01  リトライロジックが上限なしで動作、1秒間隔で叩き続ける
07:00  翌朝、Anthropic から「使用量が上限に近づいています」の通知
07:05  確認すると 28,000 回の API コールで $800 の課金が発生

復旧手順

# 1. 即座にプロセスを停止
pkill -f "node batch-process.js"

# 2. 課金状況を確認して Anthropic サポートに連絡
# → 誠意ある対応で一部返金されるケースあり

# 3. 使用量アラートを設定
# Anthropic コンソール → Usage Limits → Set monthly budget alert

再発防止

// utils/retry.ts — 必ずこのユーティリティを使う
export async function withRetry<T>(
  fn: () => Promise<T>,
  { maxAttempts = 3, baseDelayMs = 1000, maxDelayMs = 30000 } = {}
): Promise<T> {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (attempt === maxAttempts) throw err;
      const delay = Math.min(
        baseDelayMs * 2 ** (attempt - 1) + Math.random() * 500,
        maxDelayMs
      );
      console.warn(`Retry ${attempt}/${maxAttempts} in ${Math.round(delay)}ms`);
      await new Promise(r => setTimeout(r, delay));
    }
  }
  throw new Error("unreachable");
}
<!-- CLAUDE.md -->
## API コールのルール
- リトライは最大3回、exponential backoff 必須
- while(true) + API コールは絶対禁止
- バッチ処理は件数上限を明示的に設定
- 本番実行前に --dry-run でスモークテスト

事例6: デプロイ後の依存関係破損でサービス全停止

タイムライン

15:00  「パッケージを最新バージョンに更新して」と指示
15:01  npm update を実行、package-lock.json が大幅に変わる
15:05  ローカルビルドは通過
15:10  本番デプロイ実行
15:12  本番で依存パッケージのメジャーバージョン不整合が発生、起動失敗
15:12  503 エラー、サービス全停止
15:30  前のバージョンにロールバック完了

復旧手順

# Vercel / Cloudflare Pages の場合: 前のデプロイを即座に reactivate
# → ダッシュボードから 1クリックでロールバック可能

# git revert でコードも戻す
git revert HEAD~1
git push

# package-lock.json を以前のバージョンに戻す
git checkout HEAD~1 -- package-lock.json
npm ci  # package-lock.json を厳密に使う

RCA

  • 直接原因: npm update がメジャーバージョンを上げ、breaking change が発生
  • 根本原因: ステージング環境でのデプロイ確認をスキップした

再発防止

<!-- CLAUDE.md -->
## パッケージ管理ルール
- npm update は禁止 (npm update --save-dev のみ条件付きOK)
- パッケージのメジャーバージョンアップはユーザー確認必須
- デプロイ前に必ずステージング環境で動作確認
- 本番デプロイ後5分間はエラーログを監視する

事例7: 権限設定ミスで全ユーザーのデータが閲覧可能に

タイムライン

11:00  「管理画面の API エンドポイントにユーザー一覧を追加して」と指示
11:05  /api/admin/users が認証チェックなしで実装される
11:10  本番デプロイ
11:10  誰でも全ユーザーの個人情報にアクセス可能な状態が発生
13:30  セキュリティ監査ツールが未認証エンドポイントを検出
13:35  エンドポイントを即座に無効化

復旧手順

# 1. 即座に該当エンドポイントを無効化
# nginx: location /api/admin { return 403; }

# 2. アクセスログを確認して漏洩範囲を特定
grep "/api/admin/users" /var/log/nginx/access.log | \
  awk '{print $1}' | sort | uniq -c | sort -rn

# 3. 影響ユーザーへの通知 (GDPR / 個人情報保護法の要件確認)

# 4. 認証ミドルウェアを追加してデプロイ

RCA

  • 直接原因: 「管理画面に追加して」という指示で認証の要件が伝わらなかった
  • 根本原因: CLAUDE.md にセキュリティ要件が記載されていなかった

再発防止

<!-- CLAUDE.md -->
## API セキュリティ必須要件
- /api/admin/* のエンドポイントには必ず管理者認証チェックを実装
- /api/user/* には必ずログイン認証チェックを実装
- 認証なしでアクセスできる API は /api/public/* のみ
- 新しい API を追加する際は認証レベルをコメントに明記

障害対応の共通フロー (ポストモーテムテンプレート)

# ポストモーテム: [障害タイトル]

## サマリー
- 発生日時: YYYY-MM-DD HH:MM
- 検知日時: YYYY-MM-DD HH:MM
- 復旧日時: YYYY-MM-DD HH:MM
- 影響範囲: (ユーザー数/機能/期間)
- 重大度: P0/P1/P2/P3

## タイムライン
| 時刻 | 出来事 |
|------|--------|
| HH:MM | 障害発生 |
| HH:MM | 検知 |
| HH:MM | 対応開始 |
| HH:MM | 原因特定 |
| HH:MM | 復旧完了 |

## 根本原因
- 直接原因:
- 根本原因:
- 拡大原因:

## 再発防止策
| 対策 | 担当 | 期限 |
|------|------|------|
| | | |

## 学び

まとめ: 障害を防ぐ最低限の設定

// 今すぐコピーして .claude/settings.json に貼る
{
  "permissions": {
    "deny": [
      "Bash(rm -rf ~*)",
      "Bash(rm -rf /*)",
      "Bash(git push --force *main*)",
      "Bash(git push --force *master*)",
      "Bash(git push -f *main*)",
      "Bash(DROP TABLE*)",
      "Bash(TRUNCATE *)",
      "Bash(curl * | bash)",
      "Bash(wget * | sh)"
    ],
    "ask": [
      "Write(**)", "Edit(**)",
      "Bash(rm*)", "Bash(git commit*)",
      "Bash(git push*)", "Bash(*deploy*)",
      "Bash(npm install*)", "Bash(*migrate*)"
    ]
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash(git add*)",
        "hooks": [{ "type": "command",
          "command": "git diff --cached --name-only | grep '\\.env' && echo '🚨 .env 検出!中止' && exit 1 || exit 0" }]
      },
      {
        "matcher": "Bash(rm*)",
        "hooks": [{ "type": "command",
          "command": "echo '⚠️ 削除コマンド検出。5秒後に実行。Ctrl+C で中止。' && sleep 5" }]
      }
    ]
  }
}

本番障害は「設定を入れる30分」を惜しんだ結果起きます。この記事の7事例は全て、適切な settings.jsonCLAUDE.md があれば防げました。

関連記事

参考資料

#claude-code #incident #production #sre #security #postmortem

Claude Codeをもっと活用しませんか?

実務で使えるプロンプトテンプレート50選。コピペですぐ使えます。

無料プレゼント

無料PDF: Claude Code 5分でわかるチートシート

メールアドレスを登録するだけで、A4 1枚のチートシートPDFを今すぐお送りします。

個人情報は厳重に管理し、スパムは送りません。

Masa

この記事を書いた人

Masa

現役DX室長|Claude Code でゼロから多言語AI技術メディア運営中。実務直結の自動化、AI開発相談・研修受付中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。