Claudeの障害に備える:3か月50件を集計したらAPI障害の6割は1モデルだけ。Claude Codeの予備モデルとAPIキーの使い分け

当ページのリンクには広告が含まれています。

11月13日、Claudeは日本時間の夜に2回止まりました。18時58分からの約2時間はclaude.aiとClaude Codeにログインできず、22時9分からはAPIを含めてエラーが増えました。同じ日の未明には、Microsoft 365の大規模障害に巻き込まれて、ClaudeのMicrosoft 365コネクタも使えなくなっています。

次に止まったとき、何を用意しておけば仕事を続けられるのか。Claudeのステータスページに残る直近50件の障害(8月14日〜11月13日)を、どのサービスが止まったかで分けて数えました。止まり方は3つの型に分かれ、型ごとに効く備えが違います。

目次

結論:止まり方は3つ。型ごとに効く備えが違う

  • 特定のモデルだけが止まる:APIに影響した29件のうち17件がこの型でした。Claude Codeに予備のモデル(fallbackModel)を設定しておけば、別のモデルで作業を続けられます。
  • ログインやClaude Code側だけが止まる:11月13日夜のログイン障害を含む4件(計6.4時間)は、Claude Codeが使えなくてもAPIは止まっていませんでした。APIキーを事前に作っておけば、その間も従量課金で使えます。
  • 全体やコネクタが止まる:APIを含む全体の障害が7件あり、手元の設定では避けられません。Microsoft 365などのコネクタは、相手側のサービスが止まればClaudeが正常でも使えません。
  • 日本の業務時間に当たることは少ない:日本時間の平日9〜18時に重なったAPI障害は、3か月で2件(計5.0時間)だけでした。特定のモデルだけの17件は、1件も重なっていません。

11月13日に止まったもの

Claudeのステータスページの記録(11月14日0時10分取得、時刻は日本時間)では、11月13日に次の3件が出ています。

  • 2時52分〜(取得時点で未解決)Microsoft 365コネクタ:メール・予定表・検索のツールが大半のリクエストで失敗し、Claude for Outlookも影響を受けました。原因はMicrosoft 365側の障害とされています。
  • 18時58分〜20時58分 ログインの障害:claude.aiが大規模停止、Claude CodeとClaude Console(platform.claude.com)が部分停止でした。影響を受けたサービスにAPIは入っていません。20時5分に修正が入りました。
  • 22時9分〜(取得時点で未解決)APIを含む障害:claude.ai・API・Claude Codeでエラーが増え、23時36分には回復が見え始めました。ただ、コネクタ(Claude Docs・Sheetsを含む)、Claude in Meetings、Managed Agents、Claude Codeからのコネクタ利用は、劣化が続いていました。

ログインの障害とAPIを含む障害は、どちらも「Claude Codeが使えない」と見えますが、中身が違います。前者ならAPIキーで回避でき、後者なら回避できません。

3か月50件を、止まったサービスで分けた

ステータスページが公開している直近50件の記録(incidents.json)を、障害ごとに「どのサービスが影響を受けたか」で分けました。サービス別の件数は、claude.ai 38件、Claude Cowork 36件、Claude Code 33件、API 29件、Console 8件です。1件の障害が複数のサービスにまたがるため、合計は50件を超えます。

  • APIも影響を受けた:29件。障害名が「Elevated errors for Claude Sonnet 5.5」のように特定のモデルを名指ししたものが17件(計11.9時間)、「multiple models」が5件(計11.1時間)、サービス全体や機能の障害が7件(計12.6時間、未解決の1件は時間に含めず)。
  • Claude Codeは影響を受け、APIは受けなかった:6件。ログインや全体の不具合が4件(11月13日の2.0時間、9月1日の1.2時間、8月29日の3.0時間、8月25日の0.2時間)、GmailとGoogleカレンダーのコネクタが2件。
  • claude.aiやCoworkなどだけ:15件。Consoleの障害(148.6時間)や、Windows版Coworkの不具合(99.3時間)のように長引いたものもありますが、ターミナルでのClaude Codeの利用には直接響いていません。

日本時間の平日9〜18時に重なった障害は9件ありました。このうちAPIに影響したのは9月22日(1.6時間)と8月24日(3.4時間)の2件で、どちらも複数モデルの障害です。多くの障害は日本の夜から未明、米国の日中に起きていました。

注意点もあります。ステータスページの記録は、実際の影響を全部表しているとは限りません。10月7日の支出上限の誤判定では、記録上は影響なしでサービス区分も空なのに、APIやClaude Codeの要求が拒否されたとQiitaの分析記事が指摘しています。モデル別の分類も、障害名の書き方から機械的に分けたものです。

備え1:Claude Codeに予備のモデルを設定する

特定のモデルだけが止まる型には、Claude Codeのフォールバックモデルが効きます。主のモデルが過負荷・利用不可・再試行しないサーバーエラーを返したとき、指定した順に別のモデルで同じリクエストを送ります。普段使う設定ファイル(~/.claude/settings.json)に次のように書くと、毎回のセッションで有効になります。

{
  "fallbackModel": ["sonnet", "haiku"]
}

その場だけ試すなら、起動時にclaude --fallback-model sonnet,haikuと指定します。予備に並べられるのは3モデルまでです。手元のClaude Code 2.1.322で、存在しないモデルを主に指定して確かめました。予備なしでは「There’s an issue with the selected model」で止まり、--fallback-model haikuを付けるとHaiku 5.5に切り替わって「OK」と返りました。

使う前に知っておきたい条件が3つあります。

  • 切り替わらないエラーがある:認証、請求、レート制限、リクエストの大きさ、通信のエラーでは切り替わりません。ログインの障害や支出上限の停止には効きません。
  • 切り替えはそのターンだけ:次のメッセージでは、また主のモデルから試します。予備の設定は起動時にも/statusにも表示されず、切り替わったときの通知で初めて分かります。
  • 予備は2つ並べる:1つの障害で複数のモデルがまとめて止まることがあります。10月24日には、Fable 5.1・Sonnet 5.5・Haiku 5.5が同時にエラーを返しました。予備を1つだけにすると、それも止まっている場合があります。

今回の実験は存在しないモデルでの切り替えです。本物の障害で返る過負荷やサーバーエラーで切り替わるところは、再現できないため確かめていません。

備え2:ログインの障害にはAPIキーを用意しておく

Claude Pro・Max・Teamなどのサブスクリプションでは、Claude Codeは/loginでのログインを使います。認証のドキュメントによると、環境変数ANTHROPIC_API_KEYがあれば、サブスクリプションのログインより優先されます。対話モードでは初回に使うかを聞かれ、-pでは常に使われます。ログインの障害中でもAPIが動いていれば、Claude Consoleで作ったAPIキーで作業を続けられます。

# このターミナルだけAPIキーで起動する(キーは事前にClaude Consoleで作っておく)
export ANTHROPIC_API_KEY="(Consoleで作ったキー)"
claude
# /status で、APIキーが使われているかを確認する
# 終わったら unset ANTHROPIC_API_KEY でサブスクリプションに戻る
  • キーは平常時に作る:11月13日の障害ではConsoleも部分停止していました。止まってから作ろうとしても作れないことがあります。
  • 料金は従量課金:サブスクリプションの枠ではなく、APIの利用料が別にかかります。Consoleで支出上限を決めておくと安心です。
  • 使えなくなる機能がある:APIキーで動かしている間は、claude.aiのコネクタや/scheduleは使えません。
  • キーを設定ファイルに書かない:常に環境変数へ入れておくと、意図せずサブスクリプションより優先されて課金されます。また、キーを書いた設定ファイルが共有されると漏れます。設定ファイル経由の漏えいはsettings.local.jsonの記事で詳しく扱っています。

手元にAPIキーがないため、APIキーでの起動そのものは試していません。上の手順はドキュメントの記述に基づきます。

備え3:全体の障害とコネクタの障害は、作業の置き場所で備える

APIを含む全体の障害(3か月で7件、計12.6時間)は、モデルを替えてもキーを替えても避けられません。組織でAmazon BedrockやGoogle Cloud、Microsoft Foundry経由のClaudeを契約していれば別の経路になりますが、Anthropicのステータスページはそれらを扱っておらず、各障害の間に動いていたかは記録から確かめられません。

コネクタの障害は、Claudeが正常でも起きます。11月13日のMicrosoft 365コネクタのように、相手のサービスが止まればClaudeからメールや予定表を読めません。コネクタに頼る作業は、元のサービスが止まったときに手で進める段取りも決めておく必要があります。どちらの型でも、作業の途中経過をファイルやリポジトリに残しておけば、復旧後や別のツールですぐに再開できます。

自分で数え直す

集計に使ったスクリプトです。標準ライブラリだけで動きます。ステータスページのJSONを保存して渡すと、型ごとの件数と時間、日本時間の平日日中に重なった件数を出します。--listを付けると障害名も並べます。

curl -s https://status.claude.com/api/v2/incidents.json -o incidents.json
python3 claude_incidents.py incidents.json --list
"""Claudeのステータスページの障害履歴を、影響したサービスの組合せと日本時間の平日日中で集計する(標準ライブラリのみ)。

使い方: python3 claude_incidents.py incidents.json [--list]
  incidents.json  https://status.claude.com/api/v2/incidents.json を保存したもの(直近50件)
  --list          組合せごとに障害名・開始時刻(日本時間)・所要時間を並べる
所要時間は started_at から resolved_at まで。未解決の障害は所要時間に数えない。
平日日中は日本時間の月〜金 9〜18時(祝日は考慮しない)。ステータスページの区分(components)をそのまま使う。
"""
import argparse
import collections
import json
from datetime import datetime, timedelta, timezone

JST = timezone(timedelta(hours=9))
API, CODE, WEB = "Claude API (api.anthropic.com)", "Claude Code", "claude.ai"
WORK_START, WORK_END = 9, 18
MODEL_PREFIXES = ("Elevated errors for ", "Degraded performance for ", "Intermittent error spikes for ",
                  "Elevated errors on Fable", "Elevated errors on requests to multiple models")
MODEL_NAMES = ("Opus", "Sonnet", "Haiku", "Fable", "Mythos")


def parse(text):
    return datetime.fromisoformat(text.replace("Z", "+00:00")) if text else None


def group_of(names):
    if API in names:
        return "APIも影響" if CODE in names else "APIのみ(Claude Codeは影響なし)"
    if CODE in names:
        return "Claude Codeは影響・APIは影響なし"
    return "claude.ai・Coworkなどのみ"


def scope_of(name):
    """障害名から、特定のモデルだけか(モデルを替えれば続けられた可能性)を分ける。"""
    if not name.startswith(MODEL_PREFIXES):
        return "サービス全体・機能"
    return "複数モデル" if "multiple models" in name else "特定のモデル"


def work_minutes(start, end):
    """日本時間の平日9〜18時に重なる分数。"""
    total, cursor = 0, start.astimezone(JST)
    end = end.astimezone(JST)
    while cursor < end:
        day_start = cursor.replace(hour=WORK_START, minute=0, second=0, microsecond=0)
        day_end = cursor.replace(hour=WORK_END, minute=0, second=0, microsecond=0)
        if cursor.weekday() < 5:
            overlap = min(end, day_end) - max(cursor, day_start)
            total += max(overlap.total_seconds(), 0) / 60
        cursor = (cursor + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
    return total


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("path")
    parser.add_argument("--list", action="store_true")
    args = parser.parse_args()
    incidents = json.load(open(args.path, encoding="utf-8"))["incidents"]
    starts = [parse(i["started_at"] or i["created_at"]) for i in incidents]
    print(f"対象: {len(incidents)}件 / {min(starts).astimezone(JST):%Y-%m-%d}〜{max(starts).astimezone(JST):%Y-%m-%d}(日本時間)")
    stats = collections.defaultdict(lambda: {"count": 0, "hours": 0.0, "work": 0, "work_hours": 0.0, "rows": []})
    per_component = collections.Counter()
    for incident, start in zip(incidents, starts):
        names = {c["name"] for c in incident["components"]}
        per_component.update(names)
        row = stats[group_of(names)]
        row["count"] += 1
        end = parse(incident["resolved_at"])
        hours = (end - start).total_seconds() / 3600 if end else None
        if hours is not None:
            row["hours"] += hours
            minutes = work_minutes(start, end)
            row["work"] += minutes > 0
            row["work_hours"] += minutes / 60
        row["rows"].append(f"{start.astimezone(JST):%m-%d %H:%M} {'未解決' if hours is None else f'{hours:.1f}h'}"
                           f" [{incident['impact']}] [{scope_of(incident['name'])}] {incident['name']}")
    api = [i for i in incidents if API in {c["name"] for c in i["components"]}]
    scopes = collections.Counter(scope_of(i["name"]) for i in api)
    print("APIに影響した障害の内訳: " + " / ".join(f"{k} {v}件" for k, v in scopes.most_common()))
    print("サービス別の件数: " + " / ".join(f"{name} {count}" for name, count in per_component.most_common()))
    for group, row in sorted(stats.items(), key=lambda item: -item[1]["count"]):
        print(f"## {group}: {row['count']}件 / 合計{row['hours']:.1f}時間 / 平日日中に重なった{row['work']}件・"
              f"{row['work_hours']:.1f}時間")
        if args.list:
            print("\n".join("  " + line for line in row["rows"]))


if __name__ == "__main__":
    main()

このJSONに入るのは直近50件だけです。障害が多い時期は対象期間が短くなるので、出力の1行目で期間を確かめてください。

確かめていないこと

  • 本物の過負荷やサーバーエラーで、フォールバックモデルに切り替わるかは試していません(存在しないモデルでのみ確認)。
  • APIキーでの起動と、ログイン障害中にそれで作業できるかは試していません。
  • 「multiple models」の障害で、どのモデルなら動いていたかは記録から分かりません。
  • Bedrock・Google Cloud・Microsoft Foundry経由のClaudeが、各障害の間に動いていたかは確かめていません。
  • 11月13日22時9分からの障害とMicrosoft 365コネクタの障害は、取得時点で解決していませんでした。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

IT業界で10年、ソフトウェア開発をしてきたエンジニアです。今もChatGPT、Codex、Claude Codeを中心に、開発の効率化や生産性向上にAIをフル活用しています。応用情報技術者。登壇や執筆もしています。このサイトでは、仕事でAIを使う立場から、料金や利用条件、使い方を調べてまとめています。

目次