Claude を使い始めたとき、クオリティゲートはすでに整っていました。warning ゼロ、TypeScript strict モード、ESLint のすべてのルールを error 扱い、コミットごとに lint-staged。AIが絡んでくるよりも何年も前から、このワークフローで仕事をしてきました。
「warning が数個あるくらい問題ない」でもなく、「あとでまとめて掃除する」でもなく。ゼロです。
linter が refactor を強制する前に Claude が生で吐く出力を見たことがあります。並んでいたのは、エンジニアがネットで愚痴っているのと同じパターン。ゲートが答えになると最初から分かっていて、実際そのとおりに効いています。
コードベースがAIに教える
AIエージェントはパターンマッチの機械であり、学習対象はコードベースの中身そのものです。warning を握りつぶし、振る舞いが曖昧なまま走っている、乱雑で一貫性のないコードベースからは、同じく乱雑で、一貫性がなく、見えないところでひっそりと間違っているAIの出力が返ってきます。モデルが想定外のことをしているわけではなく、目の前のコードベースを鏡のように映しているだけ。
warning ゼロという状態は、Claude にとってクリーンな信号です。ゲートを通り抜けたすべてのコミットが、このプロジェクトにおける「正しい」の具体例として積み上がっていき、Claude が linter を通らないコードを書いた瞬間にエラーを読み、修正し、コミットを取り直す、というサイクルが回り始めます。こちらが介入しなくても、変更のたびにAIが自分で直してくれます。
クオリティゲートはあとから取り付けるガードレールではなく、プロジェクト固有の「良い」をAIに最初から教え込むための手段そのものです。
基準はプロンプトではなく、コードに埋め込む
初期にぶつかったパターンがあります。Claude はイベントハンドラーをすべて useCallback で包んでくる。ときどきではなく、一つ残らず。これは公開されている React コードでもっともよく見るアンチパターンのひとつで、Claude が大量に取り込んでしまっているのは明らかでした。
useCallback を使うべき条件を長文のプロンプトで説明することもできましたが、選んだのはもう一段先の手でした。React 公式のフックドキュメントを Claude に読ませ、メモ化が本当に必要になる条件を一緒に決めて、その判断基準を React ファイルを開くたびに自動で読み込まれる React スキルに畳み込んでおく。その時点から、基準はセッションのたびに Claude の目の前へ自動で並ぶようになりました。
効果はその場で出ました。どのコンポーネントのどのファイルでもフックが正しく実装されるようになり、出力への信頼が一段上がりました。
このパターンは一般化できます。Claude が同じ間違いを繰り返してくるとき、答えは「もっと良いプロンプト」ではなく、頼まなくてもセッションごとに自動で読み込まれる場所に基準を埋め込むことです。
構造的に不可能にする
ESLint がコードの品質を、Claude Code のフックが振る舞いを受け持ちます。これらは役割の種類が違うものです。
Claude Code にはフックの仕組みがあり、特定のアクションの前後で動くシェルスクリプトを差し込めるようになっています。常用しているのは次の二つです。
ひとつめは、ESLint 設定を Claude に編集させないフックです。数回の試行で lint エラーを解決できないと、Claude はまれにルールそのものを変えにきますが、これは順番が完全に逆で、ルールはこちらが意図して置いたものです。このフックは ESLint 設定ファイルへの編集を一切途中で止め、本来の修正対象である元のソースコードを直すよう Claude を仕向けます。
#!/bin/bash
# Exit 2 でツール呼び出しをブロック。stderr の中身が理由として Claude に渡る。
file_path=$(jq -r '.tool_input.file_path // ""' < /dev/stdin)
if echo "$file_path" | grep -Eq '(^|/)eslint\.config\.(js|cjs|mjs|ts)$'; then
echo "BLOCKED: Do not modify eslint.config.* to fix lint errors. Fix the ESLint error in the source file where it occurs." >&2
exit 2
fi
exit 0ふたつめは、main ブランチに対する git commit と force-push のブロックです。これがないと、こちらがブランチを切り忘れているとき、Claude が作業中のコードをそのまま main にコミットして push してしまうことがあります。フックがその試みを捕まえて止め、まずはフィーチャーブランチを切るよう Claude に伝えると、Claude はブランチを作り、作業をそちらに着地させてくれます。
どちらのフックも書いたのは Claude です。ESLint 用は一分かからずに仕上がり、git 用はエッジケースを潰すのに何度かやり取りが要りましたが、bash を自分の手で書く必要は最後まで出てきませんでした。
Claude がやめてほしいことを繰り返してくるとき、答えはそれを構造的に不可能にすることです。アクションを止めるフックは、お願いベースのプロンプトと違って、決定論的に効いてくれます。
払うコスト
グリーンフィールドのプロジェクトなら、warning ゼロのコストはほぼゼロです。コードが一行もない段階でバーを置けるからです。レガシーなコードベースだと話は別で、eslint-disable コメントの掃除と、動いていると思っていた箇所から本物のバグが浮き彫りになる作業まで含めた、それなりに時間のかかる移行になります。コストは確かに発生しますが、支払うのは一度きり。それ以降は、すべてのコミットが線の正しい側に並んでいきます。
多くのチームがブラウンフィールドのプロジェクトで厳格な linting なしに回してきたのは、これが理由です。手作業の掃除はコストが見合わない。ここをAIが変えます。コードベース全体の掃除を、それまでのほんの一部の時間でやってのけてくれます。
AIの前にあった規律
このワークフローを始めた何年も前は、自分は規律のあるエンジニアでいるだけのつもりでした。蓋を開けてみると、エージェント開発に向けてできる準備のうち、いちばん効くものを積んでいたことになります。
クオリティゲートのないコードベースでAIと開発しているとき、モデルの振る舞いはプロンプトだけで管理することになります。疲れますし、当てになりません。会話ではなく決定論的な仕組みで強制できる基準が増えるほど、出力を信用できる範囲が広がり、修正に費やす時間は減っていきます。
クオリティゲートはレバレッジを生み、しかもスケールします。一度書いたルールは、誰も覚えていなくても、すべての変更とすべてのセッションで自動的に効いてくれます。
プロンプトは無視できます。決定論的なルールは無視できません。