🔥

2日間壊し続けたAIパイプライン ── Claudeの認知バイアスと人間の介入

に公開

毎朝5時に自動実行される AI リサーチパイプラインを前回の記事で紹介しました。Sonnet 単体でテーマ選定からリサーチ、レポート生成まで一気通貫。Python コード0行、Max プラン定額内で追加コスト0。

順調でした。機能拡張に手を出すまでは。

この記事は、2日間にわたる障害と復旧の記録です。技術的なデバッグの話ではあるのですが、本当に書きたいのは別のことです。AI コーディングアシスタントにもデバッグの認知バイアスがあるという発見と、人間と AI のデバッグの分業がどう機能したかという話です。


Mem0 + 2パス方式を同時に入れた


前回の記事でエージェントチーム版(Opus 司令塔 + サブエージェント並列リサーチ)を評価し、Opus の価値はテーマ選定に集中しているという結論を得ました。フルオーケストレーターとしては費用対効果が悪い。得意な部分だけ切り出して使うべきだ、と。

その翌日、2つの機能を同時に実装しました。

1. Mem0 MCP 統合

Mem0 は AI アプリケーション向けの永続メモリサービスです。Claude Code では MCP(Model Context Protocol)経由で外部ツールを接続できます。.mcp.json にサーバー定義を書くと、claude 起動時に自動で接続される仕組みです。

狙いは、毎日のリサーチの文脈をセッションをまたいで引き継ぐことです。「先週このテーマを深掘りしたから、今日はその続きを調べよう」「この分野は3回連続で選んでいるから優先度を下げよう」。そうした判断を past_topics.json のフラットなリストではなく、意味レベルの記憶として持たせたかった。

2. Opus テーマ選定 2パス方式

テーマ選定だけ Opus に任せ、リサーチは Sonnet が実行する分業方式です。エージェントチーム版の評価で見えた「Opus はテーマ選定だけが強い」をそのまま設計に落とし込んだもの。

どちらも単体では合理的な拡張です。問題は「同時に入れた」ことでした。

5回失敗して9コミット分をリバート

2パス方式を実装して手動実行。ハングする。修正して再実行。またハングする。MCP の退避コードを追加。SSH 切断で落ちる。同一ターミナルで実行。claude -p がサスペンドする。

3回目あたりで、自分が何をデバッグしているのか分からなくなっていました。

claude -p がハングしたとき、原因の候補が同時に存在していたからです。

  • Mem0 MCP サーバーの初期化がタイムアウトしているのか
  • Opus の応答が遅いだけなのか
  • ターミナルの TTY 競合なのか

1機能ずつ入れていれば、1回目の失敗で原因が特定できたはずです。2機能が絡み合っていたから、修正が次の問題を生み、問題が連鎖しました。実装の速さと検証の速さは別物だと痛感した瞬間でした。

Mem0 の根本原因と断念

9コミット分をリバートして切り分けた結果、ハングの主犯は Mem0 MCP でした。

claude -p(非対話モード)が .mcp.json を読み、npx 経由で Mem0 MCP サーバーを起動しようとして、出力ゼロのまま15分間ハングしていた。厄介だったのは、対話セッションでは正常に動いていたことです。事前テストは対話セッションでやっていたので、問題に気づけなかった。

さらに皮肉なのが本番環境(AM 5:00 の launchd 実行)の挙動です。launchd 環境では PATH が最小限で、npx が見つからない。MCP 初期化が即失敗する。結果として MCP なしで Sonnet が普通に動き、レポートが正常に生成されていた。「本番では動くが手動テストでは動かない」という逆転パターンです。

テスト環境 本番環境 差異
対話セッション claude -p(非対話) MCP 初期化の挙動が違う
ローカルターミナル launchd PATH が違う、自動アップデートと競合する
ターミナルから直接実行 Claude Code セッション内から実行 TTY を共有し claude -p がサスペンドする

対話セッションで動いたから claude -p でも動くだろう — この前提が3回壊れました。

Mem0 は一旦 .mcp.json ごと除去し、2パス方式だけを軽量に再実装しました。運用が安定したら改めて導入する予定です。


翌朝、別の壊れ方をした

AM 5:00、即死

2パス方式の軽量再実装を終え、翌朝の自動実行を待ちました。起きてログを開いたとき、一瞬意味が分からなかった。

gtimeout: failed to run command 'claude': No such file or directory

Exit 127。Pass 1 が即座に失敗し、フォールバックもなく、スクリプトがそのまま終了していました。

原因は「自動アップデートとの競合」だった

claude コマンドの実体は symlink チェーンです。

/opt/homebrew/bin/claude  (symlink)
  → ../lib/node_modules/@anthropic-ai/claude-code/cli.js  (#!/usr/bin/env node)
    → /opt/homebrew/bin/node

gtimeout は内部で execvp("claude", ...) を呼びます。このとき OS が symlink チェーンを辿る過程で、チェーンの途中が一時的に不在だった。

証拠がありました。cli.js と symlink のタイムスタンプが 06:37:47 — スクリプト実行の 05:00:01 より後です。Claude Code の自動アップデートが裏で走り、npm パッケージの入れ替え中に cli.js が消失していたと推測されます。

同じスクリプト内の claude --version は成功していました。シェルが直接実行したそのコマンドは、アップデート開始前に完了していたからです。

悪化要因: 自分で消したフォールバック

前日のリファクタリングで「テーマ選定の責務を Opus に一元化」するコミットを入れていました。このとき Sonnet フォールバックが削除されていた。Pass 1 の失敗が即 exit となり、回復手段がなかったのです。

機能の「整理」が可用性を下げる。きれいなコードが壊れやすいコードになる瞬間でした。


AI のデバッグには認知バイアスがある

2日目の障害原因を Claude Code に調査させたとき、返ってきたアプローチに違和感を覚えました。

  1. 前日(Mem0 + 2パスでスタックした日)のログを含めた広範なデータ分析
  2. WebSearch の回数削減、プロンプト最適化など、自分が直近で実装した箇所への改修提案
  3. API レイテンシやプロンプトキャッシュなど、間接的な仮説の積み上げ

「最近自分が触ったコードに原因があるはず」 — これは人間の開発者にもよく見られる認知バイアスです。AI にも同じ傾向がありました。直近の作業コンテキストが強すぎて、問題の切り分けが甘くなる。

人間の介入が効いた3つのポイント

自分が出した指摘は3つでした。

「前日のログは別原因。当てになるのは今日の成功ログだけ」

前日の障害は MCP ハングとターミナル競合で、今日の障害は gtimeout の ENOENT。まったく別の問題なのに、Claude Code は両方のログをまとめて分析しようとしていました。ノイズを除去する判断は人間のほうが速い。

「2回目は改修後。WebSearch 削減前に成功している」

Claude Code は WebSearch の回数制限を提案していましたが、2回目の手動実行(成功した方)はその修正を入れる前に動いていました。つまり WebSearch の回数は問題ではない。不要な修正を阻止する判断です。

「回数制限を撤廃して。Opus は自律的にやめ時を考えられる」

むしろ WebSearch の人為的な制限が、Opus の判断力を不必要に制約していました。過保護なガードレールを外すという判断は、AI 自身には難しい。

いずれも「問題が発生した時点の事実」に着目し、無関係な変数を排除する判断でした。

デバッグの分業が見えた

この経験から、AI と人間のデバッグにおける役割分担が見えてきました。

AI が得意なこと 人間が得意なこと
データ処理 ログの全量読み込み、パターン検出 「どのデータが関連するか」の判断
仮説生成 広く浅く可能性を列挙 「ここだけ見ろ」と焦点を絞る
修正実装 コードの書き換え、テスト作成 「その修正は不要」と止める
コンテキスト 直近の作業に引きずられる 問題発生時点の事実に立ち返る

AI が広く浅く分析しようとするとき、人間が焦点を絞る。AI が直近のコンテキストに引きずられるとき、人間が「事実はこうだ」と切り分ける。

AI は万能なデバッガーではなく、人間のフォーカスと掛け合わさって初めて効率的なデバッグになる。 AI の盲点を人間が補正する — この分業を意識するかどうかで、効率は大きく変わります。


修正して動いた

最終的な修正は4点でした。

  1. claude コマンドのパス解決 — スクリプト起動時に realpath で実パスを解決してキャッシュし、実行中の symlink 変更の影響を軽減
  2. Sonnet フォールバックの復元 — Opus 失敗時も Sonnet がテーマ選定 + リサーチを一括実行
  3. gtimeout の除去gtimeout はコマンドを別プロセスグループで実行するため、claude -p が SIGTTOU を受けて停止していた。タイムアウト制御は --max-turns のみに変更
  4. mock E2E テスト追加 — 正常パス・フォールバック2パターンの自動テスト(計28件全パス)

修正後の手動実行で、2パス方式が初めて正常に完了しました。

項目 結果
Pass 1 (Opus テーマ選定) 2分38秒
Pass 2 (Sonnet リサーチ) 8分27秒
コスト $1.76
レポート 2本生成

前回記事での Sonnet 単体は $2.15/回でしたが、プロンプトの最適化とターン数の調整により $1.76 まで下がっています。ほぼ同じコストで Opus のテーマ選定が加わった形です。以降、毎朝のレポート生成は安定稼働しています。


ソースコード

この記事で紹介したシステムの全コードは GitHub で公開しています。

https://github.com/shimo4228/daily-research

GitHubで編集を提案

Discussion