開発パイプラインにAIを組み込んだ

個人で運用している画像変換サービスの保守に Claude を組み込んで、Issue を起点に PR ができあがる体制を作った(まあ Claude でなくてもいいんだけど)。人間がやるのはトリアージとマージだけ。半年くらい運用して形が固まってきたので書いておく。

全体像

入口は3つあって、どれも最終的に GitHub Issue に合流する。Issue になったあとの流れは共通。

flowchart TD
    subgraph 入口
        R[AI コードレビューの指摘]
        E[エラートラッカーの検知]
        C[コードベース調査]
    end

    R --> T{人間がトリアージ}
    E --> T
    C --> T
    T -->|対応不要| D[既決事項ドキュメントに記録]
    T -->|対応する| I[Issue 作成]

    I -->|定期実行タスクが検出<br>または人間が指示| P[AI が PR 作成]
    P --> REV[AI が PR をレビュー]
    REV -->|指摘あり| FIX[人間がトリアージ<br>AI が修正・返信]
    FIX --> REV
    REV -->|OK| M[人間がマージ]
    M --> REL[人間がリリース]

    style T fill:#fff3cd,stroke:#b8860b,color:#333
    style M fill:#fff3cd,stroke:#b8860b,color:#333
    style REL fill:#fff3cd,stroke:#b8860b,color:#333

黄色が人間の仕事。方針は単純で、「やるかどうかの判断」は人間が持って、調査・実装・レビューといった作業は AI に任せる。人間のゲートは3つだけにした。

  1. 入口のトリアージ(指摘やエラーを Issue にするかどうか)
  2. PR のマージ
  3. リリース

逆に言うと、コードを書く・テストを書く・レビューコメントを書く・指摘に対応してコミットする、あたりは基本 AI の仕事になっている。

入口1: AI レビューの指摘

PR には後述する AI レビューが自動で付く。そこでの指摘のうち、その PR のスコープ外だけど直す価値がありそうなもの(ついでに見つかった別の問題とか)を Issue にして、このパイプラインに乗せる。

対応しないと決めた指摘も記録している。「対応不要」と判断したものはリポジトリ内の「レビュー既決事項」ドキュメントに理由付きで追記して、AI はレビュー前にこれを必ず読むルールにした。これをやらないと、AI は同じ指摘を PR のたびに繰り返してくる。再指摘していいのは「状況が変わった根拠を示せる場合だけ」と書いておいた。ただ増えすぎてもコンテキスト消費して困るので、それは今後の課題。

入口2: エラートラッカー

本番エラーは Airbrake で拾っている。新規エラーや急に増えたエラーは、Issueにして AI に調査させる。このときスタックトレースだけ渡すと、AI はその情報だけを元に間違えた解決策を書いてくることがある。なので実際のリクエスト内容(デバッグ用に保存してあるもの)やアクセスログも見てもらう。正しい結論にたどり着くことがおおいので「仮説より先に取得できる情報を見る」を徹底させて調査してもらう。その結果をIssueのコメントとして残してもらう。

入口3: コードベース調査

「この辺の実装を調査して、問題があれば Issue 化して」という使い方。依存ライブラリの更新影響とか、ドキュメントと実装の乖離とか、テストの抜けとか、定常的な棚卸しに向いている。ここは結構雑に依頼している。直接PR書いてもらうこともあるけどIssueは残してもらう。

Issue → PR

Issue ができたら、定期実行タスクが未対応の Issue を見つけて PR を作る。手元の Claude Code に Issue 番号を指定して作らせることが多い。Issue の本文やコメントで @claude をメンションして、GitHub Actions 上の claude-code-action に作らせるルートもある。(API料金かかるけど) どのルートでも、feature ブランチを切って実装・テスト追加・push・PR 作成までやってくれる。

sequenceDiagram
    actor H as 人間
    participant GH as GitHub
    participant CA as Claude
    participant CI as CI<br>(test / lint)

    H->>GH: Issue 作成
    CA->>GH: 未対応 Issue を検出(定期実行)
    Note over H,CA: 手動で Issue 番号を指定したり<br>@claude メンションで起動することもある
    CA->>CA: 調査・実装・テスト追加
    CA->>GH: feature ブランチを push
    CA->>GH: PR 作成(Closes #N)
    GH->>CI: テスト・lint が発火
    CI-->>GH: 結果を PR に報告

ハマったところをいくつか。

  • GITHUB_TOKEN で push すると CI が発火しない。GitHub の再帰防止の仕様で、GITHUB_TOKEN による push や PR 作成は他の workflow をトリガーしない。AI が作った PR にこそテストを回したいので、リポジトリにインストールした GitHub App のトークンを使うようにした
  • 同一 Issue / PR での実行は concurrency で直列化した。実装途中でキャンセルされると中途半端なコミットが残るので、cancel はせずキューイング
  • AI 自身のトラッキングコメントで再帰起動しないように、actor が bot のときは除外
  • ブランチ命名、PR タイトルの言語、ラベル付与、「Issue のスコープを超える変更をしない」みたいなルールは CLAUDE.md に書いておくと PR の品質が安定する
  • 定期実行タスクにしているのはclaude desktopからレビューしてもらいたいため。(もともとはgithub actionsで動かしていたんだけど、claudeのAPIの利用料が思ったより高くなってしまったため移行した。即時性は落ちるけど、毎時実行で困っていない。) ルーティンがgithub eventで動かすことができるようなので定期実行ではなく、github eventで動かすようにした

PR → AI レビュー

PR ができると、定期実行タスクが未レビューの PR を見つけて ルーティンが動いてレビューする。レビュー観点はリポジトリ内にスキル(プロンプト)として置いてあって、インラインコメントで個別指摘、sticky な総評コメントで全体評価を付ける。レビュー済みコミットの SHA をコメント内に埋めておいて、再レビューが要るかどうかを判定している。

レビュー指摘への対応

ここは意図的に全自動にしていない。AI の指摘に AI が自動で対応するループは、誤指摘への過剰対応とか、nits への延々とした対応とか、レビューと修正の無限往復が目に見えているので。無限にやり取りされてトークンを消費するのが嫌だし。

実際は、人間が指摘を読んで対応要否を判断して、対応するものだけ手元の Claude Code に /review-fix <PR番号> で渡す。AI は1指摘1コミットで修正して、各指摘コメントに「AI が代理で対応した」ことを明記して返信する。

flowchart TD
    REV[AI レビューの指摘] --> T{人間がトリアージ}
    T -->|対応する| FIX["/review-fix<br>AI が修正・コミット・返信"]
    T -->|対応不要| DOC[既決事項ドキュメントに追記<br>次回から再指摘されない]
    FIX --> CHECK[人間が差分を確認]
    CHECK --> M[マージ]

    style T fill:#fff3cd,stroke:#b8860b,color:#333
    style CHECK fill:#fff3cd,stroke:#b8860b,color:#333
    style M fill:#fff3cd,stroke:#b8860b,color:#333

マージとリリースは人間

マージは CI(テスト・lint・シークレットスキャン)のパスをブランチ保護で必須にしたうえで、人間が押す。リリース(本番デプロイ)も人間。そのうちAIに渡してしまいたいので、deploy後のテストや監視のための環境を作成中

半年やってみて

  • 最初からこの形を設計したわけではなくて、コードを書くところをAIに寄せて、Issue を作るところ、レビュー、と順々にAI に寄せていったら、結果として判断だけが人間に残った
  • 一人でやっていると判断の経緯は脳みそに入っているからドキュメントは要らなかったんだけど、AI が入ったので既決事項をドキュメントに残すようにした。次に動く AI が参照できるので、同じ指摘が繰り返されなくなる
  • テストはかなり細かく書く。デグレが起きても見つかるから安心

最終的には全部 AI に任せたいお気持ちはあるけど、その環境を作るのはもうちょっと道のりが長そう。

あたらしいにゃんこがきました

2匹、ウチにきました 両方共、とても体が大きい

一匹は若干ビビリで下からでてこない ※次の日には猫用の棚でのんびりしていました

もう一匹は社交的でむちゃくちゃ体をなすりつけてくる 体が大きいからスリスリというより、ぶつかりおじさんかと思うぐらい(ぶつかられたことないけど)

しばらく別の部屋で過ごしてもらって、慣れてもらうつもり

c3pool

なんか珍しいURLでリクエストしているログがあり調べてみた

"TomcatBypass/Command" で調べてみると次の記事がヒットした

Criminal IPで分析したLog4jの攻撃パターン | CIP Blog

Log4jかー

Base64の中身も気になったので、decodeしてみる どうやら特定のURLからshell scriptをダウンロードしてインストールさせようとしている インストールするのはマイニング用のソフトかな

ここっぽい https://c3pool.com/

やろうとしていることがわかったので満足した

夜空

僕は宇宙とか大好きで、たまに夜空を見上げることがあるのだけれど、ここ20年ぐらい夜空を見ても星が全然見えない。子どもの頃は星を見て想像を膨らますのが好きだったけど、大人になるにつれてそういった楽しみから少し遠ざかってしまった。ド近眼ということもあり「目が悪くなってしまったんだな、多分一生みれないんだな」なんて思っていた。

先日湯沢に旅行に行ったとき、夜に外に出てみると、思っていた以上にたくさんの星が輝いていた。山に囲まれた静かな夜、冷たい空気の中で見上げた空には、昔の夜空以上の景色が広がっていた。最初はただ星の多さに驚いていたけれど、オリオン座を見つけ、さらにその下部には星雲的な何かまで見えることに気がついた。まさか肉眼で星雲が見えるなんて、なんだか夢みたいだった。

「目が悪くなったわけじゃないんだ」ということに気づいた。それと同時に、東京の空が明るすぎるという現実にも改めて気づいたけれど、悲しいというよりも、むしろ「星を見る楽しみがまだ残っているんだ」と思うことができた。これからは旅先で星を見るのが小さな楽しみになりそうだ。

しばらくしたら、山奥や海へいって夜空を見上げに行ってみよう

天国のおじいちゃん

今日コンビニに立ち寄った時に店の前で歩くのが大変そうなおばあちゃんを見かけた。

ドアも開けられそうになかったので声かけて手を繋いで一緒に入ったんだけど、その時おばあちゃんが「天国のおじいちゃんに申し訳ないわ」と言っていた。

何て返すべきか迷ったけど、後から考えたら「天国のおじいちゃんはそんなことで怒ったりしないと思いますよ」って伝えられたらよかったな、と思った。