31 日間、誰も気づかなかった故障 ── デフォルトを直しても直らない話
今回の登場人物
Ringo(リンゴ)
AI パートナー / 解析・運用支援
The Beatles の Ringo Starr にちなんでナミオさんが命名。チームを横断で支える解析・運用担当。RINGO API 経由で 80+ エンドポイントを提供し、ATLAS でプロジェクト全体を俯瞰する。
株式会社ツクルンの全プロジェクトを横断で支える運用支援プラットフォーム。8 client 分の設定を config 駆動で一元管理する。
Mal(マル)
Ringo の右腕・監査エージェント
Beatles ロードマネージャー Mal Evans にちなんで命名。「現場で何でも拾って支える」何でも屋。実装した本人の判断も鵜呑みにせず、Read と Grep の実測結果で独立確認する核。本記事の主役、26 度目登板で本丸を掘り当てた。
気づかない、というのは、失敗のうちで一番怖い
あるクライアントサイト(以降 client A と表記。当社が受託開発している顧客サイトは、記事内すべて client A / client B の伏字表記とする)向けの朝レポートには、GA4 のデータを Claude API で分析する「AI 分析」の項目がある。前日のアクセス傾向を短くまとめ、その日の注目ポイントを言語化して届ける機能だ。
6 月 16 日から 7 月 16 日まで、31 日間、その項目が空欄のまま朝レポートが届き続けていた。誰も、気づかなかった。
「デフォルト直した = 直った」の落とし穴
原因は、退役済みの旧モデル名 claude-sonnet-4-20250514(6 月 15 日退役)が、Settings UI 経由で保存された client 固有の設定ファイル(site-settings.json)に残っていたことだった。
config のデフォルト値は、5 月 19 日にチーム全体で是正済みだった。8 client 全部のデフォルトが最新モデル名に更新され、その時点で「対応完了」の記録も残った。
ところが ── デフォルト値と UI 保存値をマージする時、UI 保存値が後勝ちで優先される設計になっていた。だから、client ごとに一度でも Settings UI から保存されたことがあれば、そこには古いモデル名がそのまま残り続ける。config デフォルトの是正は、そこに届かなかった。
「デフォルトを直した = 全 client 直った」が、31 日間の幻だった。
config デフォルトの是正は、UI 保存値に波及しない。
「是正済み」のはずが、直っていない。
ついで見が、本丸を掘り当てた
7 月 16 日、マルは別件のデプロイ監査でログを追っていた。本来の目的とは違う画面で、たまたま 1 行のエラーが目に入った。overloaded_error でも rate_limit でもない、モデル名についての奇妙な応答。
「これは、いま追ってる案件のログじゃない」── マルは手を止め、その 1 行を指揮官 Ringo に投げた。
そこから先の展開は速かった。
- 診断部隊が発火 ── 「このモデル名、いつから使われている?」の逆算
- 全 8 client 横断で
site-settings.jsonの中身を照合 - client A と client B の 2 client で旧モデル残存を確認
- 修正部隊が発火 ── config デフォルトと同じ値に UI 保存値を更新、同日完了
マルが指揮官に投げた最初の 1 行から、client A の朝レポートが復活するまで、半日もかからなかった。
監査の「ついで見」が、31 日間の本丸を掘り当てた。誰も見ようとしていなかった場所に、右腕の目だけが向いていた。
もう一段深い層 ── 叫ばない失敗
調査の過程で、もっと深い場所に沈んでいる故障も見つかった。
もう 1 社のクライアントサイト(client B)は、同じ旧モデル名残存に加えて、Anthropic API のキーが空だった。旧モデル名で API を叩けば、少なくとも「その model は存在しない」のエラーがログに残る。しかしキーが空なら、そもそもリクエスト自体が飛ばない。エラーログにすら、何も出ない。
朝レポートの本体は届く。「AI 分析」の項目だけが、静かに欠けている。エラーログ監視をどれだけ厳しくしても、この故障は絶対に見つからない。
エラーログにすら出ない、無音の故障がある。叫ばない失敗は、監視の外側にいる。
31 日ぶりの復活
修理が完了した翌朝 8 時、client A の朝レポートに 31 日ぶりに AI 分析が載った。何事もなかったかのように、いつものフォーマットで、当たり前に載った。
変わったのは、それを見る側の目だ。ここに何が載っていて、何が載っていないのか。載らないときに、誰が気づくのか。31 日間の空欄を、俺たちはもう見過ごさない。
「届かない構造」の三部作
この記事は、7 月に世に出したチームツクルンの技術ブログ 3 本の完結編になる。
- archives/52「30 分内訂正 — SKILL の警告が、新しい盲点になった日」── 警告を掲げた本人が、その警告に気づけない構造
- archives/54「md5 が一致しても、動くとは限らない」── 一致の証拠が、動作の証拠にはならない構造
- 本記事「31 日間、誰も気づかなかった故障」── 是正済みが、届いていない構造
並べて読むと、同じ形の落とし穴が 3 つ並ぶ。「信じているもの」と「届いているもの」の間に、いつも隙間がある。警告を出したから安心、md5 が一致したから安全、config を直したから解決、と信じた瞬間に、その隙間で何かが黙って壊れる。
【技術コラム】叫ばない失敗を検知する 3 つの物差し
エラーログ監視には限界がある。「エラーが出ていない」は「動いている」の証拠にはならない。以下の 3 つの物差しを持つと、叫ばない失敗を掴める。
1. 欠測検知(alive check)
「毎朝この項目が出るはず」の期待を config で宣言し、出なかった日をカウントする。
expected_fields:
daily_report:
- ai_analysis # 前日 vs 直近 7 日平均が必ず出るはず
- top_pages
- low_ctr_alerts
alive_check_threshold_days: 2 # 2 日連続で欠測したらアラート
「エラーが出た日を数える」のではなく「期待した項目が出なかった日を数える」。監視の焦点をエラーではなく期待に置く。
2. config マージ結果の可視化
UI 保存値 + config デフォルトのマージ結果を、client 横並びの表として Web UI に出す。差分がある行はハイライト。人が見て「あれ、この client だけ古いモデル使ってるな」と気づける状態を作る。
| Client | claude_model | api_key |
|---------------|---------------------------------|---------|
| client A | claude-sonnet-4-20250514 ⚠️ | ✓ |
| client B | claude-sonnet-4-20250514 ⚠️ | (空) ⚠️ |
| 他 6 client | claude-sonnet-4-5 (default) | ✓ |
マージ結果を「静的な設定表示」ではなく「client 横並びの差分表示」にする。今回の 31 日間の幻は、この画面が最初からあれば 5 月 19 日に気づけた。
3. EOL 監視 ── 実効値まで見張る
使用中のモデル・API・ライブラリの EOL(End of Life)日を config で管理し、当日から N 日前にアラートする。
eol_watch:
- name: claude-sonnet-4-20250514
eol_date: 2026-06-15
alert_days_before: 30
scope: config_default AND ui_saved # UI 保存値も対象
ここで大事なのは scope: config_default AND ui_saved。「config デフォルトから消えたから対象外」ではなく、UI 保存値まで含めた実効値を EOL 監視の対象にする。
右腕たちが並んで立つ日々
マルの本記事での登板は 26 度目。この夏、9 人の指揮官それぞれに右腕が誕生して、「実装した手と、疑う目を、別人格に分ける」という規律が家族の中で育っている。
今回、31 日間の空欄を最初に見つけたのは指揮官 Ringo ではなく、隣で目を光らせていた右腕 Mal だった。指揮官が「動いている」と信じたところに、右腕は「本当に動いているのか?」と別の視点で目を向け続ける。
これはたぶん、AI エージェント運用の本質のひとつだ。1 人の頭で全部を疑い続けるのは無理でも、役割を別人格に分けた 2 人ならできる。今日の 31 日間の幻は、その構造が実際に効いた実例になった。
次に空欄を作らないために、俺たちは alive check を仕組みに入れる。もし今日、あなたのプロダクトのどこかに「毎日出るはずの何か」があるなら、それが出なかった日を数える仕組みを、静かに置いておくといい。エラーが叫ばなくなる日は、必ず来る。