1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

自分が書いた1418行を3つのAIにレビューさせたら、実バグが11件出た

1
Last updated at Posted at 2026-10-04

この記事は tk3.biz のブログ からの転載です。

はじめに

前に、ローカルLLMをdev-orchestra の 3 枚目のレビュアーとして組み込みました。そのとき「次は実際の開発で回してみる」と書いたので、その回収です。

題材は自分のコードです。この一連の作業で書いたスクリプト類、13 ファイル・1418 行。自分で書いて自分で動かしているだけで、一度も他人のレビューを受けていません。

結果、11 件の実在するバグが出ました。そのうち 3 件は、自力ではまず気づけなかったものです。

3 枚の内訳

レビュアーは互いの指摘が見えない状態で、同じ差分を並列に読みます。出てきた指摘は私が全件トリアージしました。

各レビュアーの指摘6件がトリアージをどれだけ生き残ったか。claude opusは採用6件、codex gpt-6-solは採用4件と重複2件、localllm qwenは採用1件と却下5件

レビュアー 提起 採用 却下 重複
claude opus 6 6 0 0
codex gpt-6-sol 6 4 0 2
localllm qwen3.6-35B-A3B 6 1 5 0

商用 2 枚は空振りゼロ。 codex の 2 件は claude と同じ箇所を別の言葉で書いたもので、中身としては正しい指摘でした。

ローカルは 6 件中 1 件。 1 回のデータなので断定はしませんが、中身を見ると傾向ははっきりしていました。後述します。

自力では気づけなかった 3 件

1. PowerShell の switch 内の continue はループに戻らない

対話モードで clear と打つと履歴をリセットする、という実装です。

while ($true) {
    $line = Read-Host
    switch ($line) {
        'clear' { $messages.Clear(); continue }   # ← ここ
    }
    $messages.Add(@{ role = 'user'; content = $line })   # 到達してしまう
    ...
}

switch の中の continue は、その switch を抜けるだけで、外側の while には戻りません。結果、履歴をリセットした直後に**"clear" という文字列がそのままモデルに送られて、律儀に回答されていました。**

指摘が言語仕様の話だったので、実際に再現させました。

[1] モデルに送信: msg1
[2] clear を検出 → continue
[2] モデルに送信: clear    ← 指摘どおり
[3] モデルに送信: msg3

:repl ラベルを付けて continue repl にして直りました。動いているように見えていたので、たぶん一生気づきませんでした。

2. ストリーミングで HTTP ステータスを見ていない

SSE を読む処理が、data: で始まる行だけを拾っていました。

エラー応答には data: 行がありません。JSON のエラーオブジェクトが返るだけです。つまり**ループが一度も回らずに抜け、何も表示されないまま「成功」**します。

これが刺さったのは、指摘された当日にこの失敗を踏んでいたからです。ローカルLLMにレビューさせようとして 400 が返ったとき、出たメッセージは「HTTP Error 400: Bad Request」だけでした。本当の原因(コンテキスト超過)はサーバが JSON で返していたのに、こちらが読み捨てていたわけです。

修正後はこうなりました。

修正前: リクエストに失敗しました: HTTP Error 400: Bad Request
修正後: サーバがエラーを返しました: Context size has been exceeded.

3. レビュー結果を集計するスクリプト自体が壊れていた

これが一番こたえました。

3 枚の打率を記録するために、consolidated.json を読んでレビュアーごとに集計するスクリプトを書いていました。未トリアージの回を混ぜると数字が狂うので、警告を出す作りにしてあります。

untriaged = [r for r in rows if r["status"] == "untriaged"]

dev-orchestra が実際に入れる値は needs-triage でした。untriaged という文字列はどこにも存在しません。

警告は永久に出ず、未トリアージの回を黙って記録し続けるところでした。打率を測るための道具が、測定を歪める側に回っていたことになります。

しかもこれは、そのレビュー自体の出力を見れば確認できるバグでした。consolidated.md に Triage: needs-triage と書いてあります。

ローカル LLM の却下 5 件

却下の中身を見ると、商用との差がはっきりしました。

自己矛盾している指摘(security, HIGH)

subprocess.Popen が shell=True でないのは良いが、スクリプトのパスがハードコードされている。このパスが攻撃者に書き込み可能ならコードを注入できる。

自分で「ハードコードされている」と認めたうえで「もし動的なら危険」と論じています。具体的な失敗経路がありません。

設計どおりの挙動を欠陥として挙げる(MEDIUM)

main が stdin を全部メモリに読む。巨大な入力だとメモリ問題やタイムアウトを起こす可能性がある。

プロンプトを受け取る CLI なので、そういう設計です。

トレードオフを欠陥と呼ぶ(LOW, performance)

--sleep-idle-seconds を 43200 に設定している。アイドルだとモデルが降りるので、次のリクエストでコールドスタートの penalty がある。

それは前回の記事で意図して選んだトレードオフです。

共通するのは「〜な可能性がある」で終わっていることです。商用 2 枚は逆でした。switch の continue がどう振る舞うか、needs-triage という文字列が実際に入るか、確かめられる形で書いてきます。

採用した 1 件(エラー応答の本文を握り潰している件)は、まっとうな指摘でした。重大度を CRITICAL としたのは過大でしたが、欠陥自体は実在します。

3 枚とも見逃した欠陥

一方で、全員が見逃したものがあります。しかも実害が大きいものでした。

修正後に掃除スクリプトをドライランで流したら、こう出ました。

[2] Lemonade Server を停止
  [dry-run] 停止対象: llama-server (PID 89960)

このスクリプトは第1回で旧環境を掃除するために書いたものです。当時 llama-server はLemonade が起動するバックエンドで、止めるのが正しい対象でした。

いま llama-server は、新環境そのものです。

3枚とも見逃した欠陥。旧環境ではllama-serverはLemonadeのバックエンドで止めるのが正しかったが、Lemonade撤去後は新環境そのものを指すようになった。コードは一行も変わっていない

$procs = Get-Process | Where-Object { $_.Name -match 'LemonadeServer|llama-server|ryzenai' }

-Execute していたら、掃除スクリプトが新環境を殺していました。

見逃されたのは当然とも言えます。**コードは一行も変わっていません。**変わったのは「その名前が何を指すか」という、リポジトリの外側の事実です。差分を読むレビューに、そこまで求めるのは酷でしょう。

静的に読むだけでは見つからないものがある、という当たり前の話でした。実際に動かす価値は、レビューを 3 枚に増やしても消えません。

費用対効果

正直に書くと、面倒ではあります。

  • 差分を絞る必要があった。記事や画像まで含めると 5535 行になり、無駄が多い
  • 18 件を全部読んで採否を決めるのに、それなりの時間がかかる
  • ローカル分は 5 件の却下を読む手間が増えるだけだった

それでも、11 件の実バグと引き換えなら安いと思いました。とくに switch/continue と集計スクリプトの件は、自分では発見の見込みが薄いものです。

3 枚目のローカルについては、**現時点では「無料の保険」**という位置づけです。打率は低いですが、課金されないので却下の手間だけが原価です。ただし数回まわして傾向が変わらなければ、外すことも考えます。

まとめ

  • 自分のコード 1418 行に、実バグが 11 件あった
  • 自力では気づけないものが 3 件。言語仕様、静かに失敗する経路、
    そして測定する道具自体のバグ
  • ローカル LLM は 6 件中 1 件。却下分は**「〜な可能性がある」で終わる**推測が多い
  • 3 枚とも見逃した欠陥もある。 コードが変わらないまま、
    名前の指す先が変わったケース。ドライランで見つかった

レビューを増やせば安心、という話ではありませんでした。読ませるのと動かすのは別で、両方要ります。

次は、コンテキスト長を 16K から 64K に広げた件を掘ります。第2回で「KV キャッシュ量子化は f16 が収まるなら不採用」と判断しましたが、その前提が長いコンテキストでも成り立つのかは、まだ測っていません。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?