🚨

エラー検知からPR作成まで自動化したら、監視設計の甘さが全部返ってきた😇

に公開

はじめに

「本番のエラーログを検知したら、AIに原因調査から修正PRまで作らせる」——エージェントが実用域に入ってから、一度は絵に描いたことがある構成ではないでしょうか。

結論から書くと、仕組みは思ったよりあっさり動きました。難しかったのは、動いた後のほうです。

この記事では、Datadogのエラー検知をトリガーにLinearへIssueを自動起票し、Claude CodeのRoutineチェーン(実装 → PR → レビュー対応)で自律対応させる仕組みを、3ヶ月ほど試験運用した記録を共有します。
設計そのものよりも 「どこまでAIに任せられて、どこから任せてはいけなかったか」 という境界線に紙面を割きます。

対象読者

  • エラー監視から修正PRまでをAIエージェントで繋ぎたい/繋ぎ始めた人
  • AIエージェントを運用フローに組み込んだものの、ノイズやレビュー負荷で苦戦している人
  • 監視SaaS × Issueトラッカー × エージェントの連携設計に興味がある人

前提知識

  • DatadogのMonitor / Error Trackingをなんとなく触ったことがある
  • Issueトラッカー(Linear など)のwebhookでなにかを起動したことがある
  • AIコーディングエージェントに実装を任せた経験がある

環境

  • Datadog(Monitor / Error Tracking / MCP)
  • Linear(ラベルと webhook)
  • Claude Code の Routine(クラウド実行)
  • 対象は Node.js / TypeScript の本番サービス

背景・課題 🤔

エラー検知の仕組み自体は前からありました。
Datadogが拾い、Slackに流れ、誰かが見て、調べて、直す。

問題はこの「誰かが見て、調べて」の部分です。
エラー対応は着手までのリードタイムが長く、しかも調査の大半が同じ手順です。
スタックトレースを読んで、該当コードを開いて、再現条件を推測して、影響範囲を確認する。
この繰り返しの部分は、コードベースを読めるエージェントが最も得意とする領域でもあります。

一方で、当時すでにチームには「Linearのラベルを変えると、登録済みのエージェントルーチンが起動する」仕組みができていました。
実装ルーチン、レビュールーチン、修正ルーチンがラベル遷移で繋がっていて、人間がやることは最後のマージだけ、という状態です。

検知(Datadog)と実行チェーン(Routine)は既にある。足りないのは、その2つを繋ぐ「起票」だけだった。

そこで、その「起票」を作り、エラー検知から修正PRまでを通しで動かして本運用に耐えるかを検証することにしました。

解決策 💡

アプローチ:起票を独立したルーチンとして切り出す

最終的なフローはこうなりました。

ポイントは、起票を実装チェーンに含めず、独立した1ルーチンとして切り出したことです。
理由は2つあります。

ひとつは責務の分離です。起票ルーチンの仕事は「何が起きたかを正確に転記すること」だけで、原因分析は書かせません。
原因調査は下流の実装ルーチンの責務であり、起票時点で憶測の原因を書いてしまうと、下流がそれに引きずられます。
同じ理由で、「リリース起因バグ」「既存バグ」といった分類ラベルも起票時には付けません。
ログだけでは判別できないものを、判別できるふりをして書かない、という線引きです。

もうひとつは入口の差し替えやすさです。
起票ルーチンのプロンプトを共通の土台にしておけば、Sentryなど別の監視サービスを繋ぐときに入口を1本足すだけで済みます。

実装1:Issue に載せるのは「マスク済みの事実」だけ 🔑

起票ルーチンがLinearに書くのは、次の3点に絞りました。

  • マスク済みのエラー詳細(メッセージ+主要スタックフレーム+推定発生箇所)
  • 元ログへのDatadog URL
  • 発生環境

生ログ全文は転記しません。本番ログにはユーザー識別子やトークンが混ざり得るので、Issueトラッカーに転記する手前でマスキングし、全文が必要な人は Datadog を見に行くという動線にしています。Issueは誰でも見られるがDatadogにはアクセス制御がある、という前提を崩さないための設計です。

実装2:ラベルをトリガーにする(ネイティブトリガーが足りない問題) 🪝

ここは環境依存の話ですが、同じ壁に当たる人がいそうなので書いておきます。

Claude Code の Routine が持つネイティブなトリガーは schedule / GitHub / API の3種だけで、Issueトラッカーのラベル変更では直接起動できません。
そこで、Linearのwebhookを受けて該当ルーチンのAPIトリガーを叩くだけの薄いWorker(Cloudflare Workers)を1枚挟んでいます。

Datadog側も同じくwebhookで起票ルーチンのAPIを叩きます。ここで地味にハマったのが認証まわりのヘッダで、Authorizationの他にAPIバージョンとベータ機能を示すヘッダが必須な点です。
1つでも欠けると当然起動しないのですが、Monitor側からは「通知は送った」ようにしか見えないため、切り分けに時間を使いました。

ポイント:安全側に倒した3つの設計 ⚠️

試験運用に入る前に決めておいてよかったのが以下です。

  • 重複制御は二段構え。 Datadog Monitor側で同一エラーを集約・再通知抑制し、さらに起票ルーチン内でもLinearの未クローズIssueを検索して、あれば起票せずコメント追記だけして止める。片方だけでは必ず漏れます。
  • 終端は必ず人間。 修正ルーチンはPR作成とレビュー指摘への対応までで停止し、マージと force-pushはしない。ここを自動化しない限り、最悪でも「無駄なPRが1本増える」で済みます。
  • 起票ルーチンには原因を書かせない。 前述のとおり、事実と推測を層で分けます。

結果:わかった境界線 📊

3ヶ月動かした結論です。
チェーン自体は期待どおり動きました。
エラーを検知し、Issueが立ち、修正PRがレビュー待ちで止まる。ここまでは想定内です。

想定外だったのは、入口の精度が甘いと、自動化はその甘さを増幅して返してくるということでした。

実例:対応不要のエラーに、6本のPRが作られた

監視クエリはAPMスパンの status:error を拾っていました。
ところが、アプリ側で catch して正しく処理しているエラーも、スパンとしては error で閉じられます。クエリが失敗した時点でスパンはerrorになり、アプリのcatchはその後に走るからです。
つまり監視クエリに handled / unhandled の区別が無い状態でした。

実際に起きていたのは、あるテーブルへの初回INSERTで稀に起きるロック競合でした。
同一ユーザーの並行リクエストが同時に同じ行を作ろうとして衝突し、敗者側はリトライ処理で再実行され、2周目で勝者が作った行を見つけて正常終了する——という流れです。
直近30日のリトライログは全件が1回目の再実行で成功しており、レスポンスも後続処理も正常に完了していました。ユーザー影響ゼロ、設計どおりの挙動です。

それでもスパンはerrorなので、監視は反応し、起票ルーチンは正直にIssueを立て、実装ルーチンは真面目に修正PRを作りました。
結果、同一事象に対して6件のIssueが自動起票されています。
しかも1つのデッドロックがORM層とドライバ層の2つのスパンに現れるため、同じ事象が2つのIssueに分裂して同時発報されていました。

さらに笑えないのが、別々に起票されたIssueから作られた2本のPRが、同じ判定関数を二重定義してコンフリクトしたことです。
片方は引数をオブジェクトで受け、もう片方は素の引数で受けていて、設計方針まで食い違っていました。エージェントは各Issueの文脈では正しく仕事をしているのに、Issue同士が同じ事象を指していることだけは知らないわけです。

終盤には、リトライのバックオフを線形から指数に変えるPRまで作られました。
ただ、そもそもリトライは1回目で必ず成功しているので、効果がないことが確認できてクローズしています。

数字で言うと、このデッドロックに限らず監視を作った直後の1日で18件が自動起票され、うち12件が「対応しない」で終わりました。

一律で黙らせることもできなかった

「じゃあDBエラーの監視を外せばいい」とはなりませんでした。
同じ監視スコープの中に、コネクションプール枯渇という本物の問題が1件だけ紛れていたからです。
これはリリース窓で実際に踏んでいる事象で、検知され続ける必要があります。
しかもプール枯渇はクエリ実行前に起きるため、ORM層のスパンには現れず、ドライバ層のサービスを監視対象から外すと拾えなくなる。

結局、既知のノイズを fingerprint 単位で除外するという、いちばん地味な方法に落ち着きました。UIからエラーを個別に無視する機能もありますが、それだと設定が監視定義に残らず、後から経緯を追えません。クエリに -@error.fingerprint:(...) を書いておけば、なぜ除外したかがコードレビュー可能な形で残ります。

任せられたもの/任せられなかったもの

工程 結果
エラー詳細の転記・マスキング 任せられた。定型なので安定している
既知パターンの原因調査と修正PR作成 任せられた。レビューして直せば使える水準
レビュー指摘への対応 任せられた。終端で止まるので安全
そのエラーが「対応すべきか」の判断 任せられなかった。入口で人間が決めるしかない
複数Issueが同一事象かの横断判断 任せられなかった。文脈が1Issueで閉じているため

AIエージェントの出力品質は、プロンプトよりも入口のフィルタ精度で決まる。ノイズを入れれば、ノイズに対して真面目な成果物が返ってくる。

まとめ 📝

  • 👉 チェーンを繋ぐのは難しくない。難しいのは入口。 検知クエリの精度がそのまま成果物の質になる。
  • 👉 監視クエリは handled / unhandled を区別する。 捕捉済みエラーを拾うと、対応不要のIssueと真面目なPRが量産される。
  • 👉 起票と実装は責務を分け、起票時に原因や分類を書かせない。 憶測が下流を引きずる。
  • 👉 重複制御は監視側とアプリ側の二段構え。 それでも「別Issueが同じ事象」は防げないので、ノイズ源そのものを潰す。
  • 👉 終端は人間。 マージを自動化しなければ、失敗は「無駄なPR1本」で収まる。

おわりに ✨

やってみるまでは「エージェントの実装品質」が課題になると思っていました。
実際に効いたのは、その手前の監視設計でした。
人間が対応していた頃は「あ、これはいつものやつね」と暗黙に握りつぶしていたノイズが、自動化すると全部チケットとPRになって返ってくる。
自動化は、運用の曖昧さを可視化する装置でもあるという実感です。

同じような仕組みを組もうとしている方は、繋ぐ前に一度、監視クエリが拾っているものを30日分ほど棚卸ししてみるのをおすすめします🙌

弊社ではこうした開発・運用フローの改善にも一緒に取り組んでくれる仲間を募集しています。興味があればぜひのぞいてみてください。

参考 🔗

※本記事の構成やクエリ例は、サービス名・テーブル名などを一般化しています。

ラッコ株式会社

Discussion