社内のCIパイプライン(コード変更を自動でビルド・検証する仕組み)に、長年グリーン表示のまま放置されているチェックはないでしょうか。エンタープライズ開発では、リポジトリ(ソースコードの保管単位)が数十を超え、レビュー担当者もチェックの中身まで追わなくなりがちです。この記事は、そうした環境で「PRチェックが本当に機能しているか」に不安を感じているエンジニア・SRE・QA担当者に向けて、実際に起きた落とし穴と確認手順を整理します。
9つのリポジトリで2864件のCIチェック実行を集計した事例があります。失敗率は約10%でした。この数字自体は大きな意味を持ちません。問題は、その裏に隠れていた「チェックが一度も正しく動いていなかった」という事実の方です。
何が起きたか:グリーンなのに機能していないチェック
該当の事例では、Vale(文章のスタイルをチェックするリンター)というツールに、独自ルールが1本組み込まれていました。「EmDashes」というルールで、原稿中の「—」(em dash、欧文で使う長いダッシュ)を検出してビルドを失敗させる想定でした。
ところが実際に公開された記事には、em dashが15箇所含まれていました。Valeはそのファイルを正しく読み込み、受動態や弱い語彙の警告は15件・10件と正常に検出していました。em dashの検出だけが常にゼロ件でした。
原因は正規表現の書き方にありました。ルールは \b(?:—|–)\b という形で、単語境界(\b)でダッシュを挟んでいました。\b は「単語文字と非単語文字の境目」に成立する条件です。em dashは記号であり単語文字ではないため、この境界条件が満たされず、マッチが一度も成立しない構造になっていました。
さらに悪いことに、CI側の実行スクリプトにも別の欠陥がありました。Valeを --no-exit オプション付きで実行し、常に終了コード0を返す設定にしたうえで、出力をgrepして error という文字列を探す形でビルド失敗を判定していました。ところが出力フォーマットを --output=line に指定していたため、出力には重要度(severity)の情報自体が含まれておらず、grepの条件は原理的に一致しようがない状態でした。
なぜ気づけないのか:原因を段階的に分解する
この落とし穴が厄介なのは、単一の見落としではなく、複数の層で「沈黙」が積み重なる構造にある点です。
1つ目の層は正規表現の設計ミスです。単語境界という概念を、記号やマルチバイト文字(日本語含む)に無条件で適用すると、意図した箇所にマッチしません。Valeには nonword: true という、単語境界の判定を外すオプションが用意されていましたが、ルール作成時にこれが漏れていました。
2つ目の層は、CIスクリプト側の「失敗判定ロジック」の設計です。ツール本体の終了コードを無視して自前でgrep判定する構成は、ログのフォーマット変更に弱い作りです。今回は出力フォーマットとgrepの検索条件がそもそも噛み合っておらず、判定ロジックが機能しない状態がビルド設定の変更と無関係に固定化されていました。
3つ目の層は、これらが「一度もアラートを出さない」形で失敗する点です。ルールが厳しすぎて誤検知を連発するなら気づけます。逆に、何も検知しないルールは、テストの正常系と見分けがつきません。ここが今回の事例の本質的な怖さです。
エンタープライズの業務システムでは、この構造がより大規模に起こり得ます。テンプレートリポジトリから新規プロジェクトを次々に複製する開発体制では、テンプレート内のCI設定の不具合が、複製先すべてに継承されます。今回の事例でも、組織内で最も失敗率が高かったのは「テンプレートとして使われているリポジトリ」でした。稼働実績の少なさゆえに失敗率という指標自体が意味を持たない、という指摘もあります。稼働開始直後の数週間に集中して失敗が出て、その後何カ月も静かになるパターンは、移行作業や環境構築のタイミングと重なっているだけで、システムが安定した証拠ではありません。
自分のプロジェクトが該当するか確認する方法
以下の観点で、手元のCI設定を点検できます。
- カスタムリンタールール(Vale・ESLint・Rubocopのカスタムルールなど)の正規表現に単語境界(
\b)を使っている場合、対象がマルチバイト文字や記号を含んでいないか確認する - CIスクリプト内で、ツール本来の終了コードを無視して独自にgrepやテキスト比較で失敗判定をしていないか確認する
- ツールの出力フォーマット(
--outputオプションなど)を変更したとき、判定ロジック側の期待する文字列・構造も同時に見直しているか確認する - 各リポジトリのCI実行履歴で「一度も失敗したことがないチェック」がないか、CIプラットフォームの実行ログ一覧(GitHub Actionsなら「Actions」タブのワークフロー別履歴)で確認する
- テンプレートリポジトリのCI設定を、複製先で個別に検証せずそのまま踏襲していないか確認する
特に「一度も失敗したことがないチェック」は要注意です。本当に品質が高いのか、判定ロジックが機能していないだけなのかは、ログを眺めるだけでは区別できません。
対策の手順
手順1: チェックを意図的に失敗させてみる
検出対象となるべき違反を含んだテスト用の変更(例えばem dashを含む文章、禁止パターンを含むコード)を用意し、実際にCIが赤くなるか確認します。これは単体テストで言う「壊れているはずのケースを壊す」確認と同じ発想です。単体テストのカバレッジがあっても、アサーションが機能していなければ意味がないのと同様、CIチェックにも「本当に失敗できるか」の検証が必要です。
手順2: 判定ロジックとツール出力の対応を確認する
--no-exit のようにツール本体の終了コードを無効化している箇所を洗い出し、代わりの判定ロジック(grep・スクリプト内比較)が、実際のツール出力フォーマットと一致しているか突き合わせます。出力フォーマットのオプションを変更した履歴があれば、そのコミットの前後で判定ロジックも一緒に変更されているか確認します。
手順3: 正規表現ベースのルールを個別にレビューする
単語境界・先読み・文字クラスなどを使った正規表現は、対象文字種(英数字・記号・日本語)ごとに動作を確認します。ローカルでツールを実行し、意図した箇所に実際にマッチしているかを目視で確認する作業が有効です。
手順4: リポジトリ横断でCI実行履歴を棚卸しする
CIプラットフォームのAPIやダッシュボードから、過去の実行結果を集計し、一度も失敗していないチェックをリストアップします。失敗回数がゼロのチェックは、正常に機能している可能性と、判定ロジックが機能していない可能性の両方があるため、優先的に手順1の検証対象にします。
手順5: テンプレートリポジトリのCI設定を独立して検証する
複製元となるテンプレートのCI設定は、複製先とは別に定期的な健全性確認の対象にします。テンプレート側の不具合は、気づかないまま複数プロジェクトに継承されるためです。
まとめ
CIの「グリーン」は、チェックが正しく機能した証拠にはなりません。判定ロジックの欠陥・正規表現の設計ミス・出力フォーマットとの不一致は、いずれも同じ「沈黙」という形で現れ、テストのパスと見分けがつかないためです。
まず着手できるのは、意図的に違反を含む変更を用意してCIを赤くしてみる確認です。あわせて、一度も失敗していないチェックの棚卸しと、--no-exit のような終了コード無効化箇所の洗い出しを進めてみてください。テンプレートリポジトリを運用している場合は、その健全性確認も忘れずに組み込んでおくと安心です。