0
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?

AIに作ってもらったGitHub ActionsのCI、5ヶ月なにも検知してなかった

0
Posted at

結論

個人リポのCIが、5ヶ月ずっとエラーになることがなかった。

優秀だったからじゃなくて、どう転んでも失敗しようがない書き方をしていたから。

そのCIは僕が書いたものじゃなくて、AIに作ってもらった。
で、正直に書くと、レビューして通したというより、出てきたものを眺めて「ちゃんとしてそう」で受け取っただけ。

しかもその間、GitHub Actions の分数は消費され続けていた。
30日で約266分、なにも検知しないまま。

普段は仕事で、人が上げてきたものをレビューして承認する側にいるんだけど、その僕が、自分がAIから受け取るときには承認と呼べる工程を踏んでいなかった。
だからこれはCIのバグの話じゃなくて、承認の事故の話。

何が動いていたか

gtd/zettelkasten/ 配下の Markdown に YAML frontmatter があるかを、push と PR のたびに全件チェックする step。
実物がこれ。

- name: YAML frontmatter の存在チェック
  run: |
    echo "=== frontmatter チェック ==="
    errors=0
    while IFS= read -r file; do
      head -1 "$file" | grep -q '^---' || {
        echo "⚠ frontmatter が見つかりません: $file"
        errors=$((errors + 1))
      }
    done < <(find gtd/ zettelkasten/ -name '*.md' -not -name '.gitkeep' 2>/dev/null)

    if [ "$errors" -eq 0 ]; then
      echo "✓ 全ファイルに frontmatter があります"
    else
      echo "⚠ ${errors} ファイルに frontmatter がありません"
    fi

先に言っておくと、僕はこれを自分で読んで気づいたわけじゃない
 →僕はコードを書かない側の人間で、正直、このソースを渡されて眺めても見つけられなかったと思う

気づいたのは5ヶ月経ってから、CI をまるごと Claude と見直していて、Claude に見つけてもらった。
この step には exit 1 がない。

errors を数えて、違反ファイル名を出して、件数も出して、成功と失敗で分岐までしている。
そのうえで何も返してないから、この step は絶対に成功する。

さらに同じ workflow のリンクチェック step には continue-on-error: true が付いていた。
つまりこの workflow は、どんな違反があっても失敗にならなくて、検知でエラーになったことは5ヶ月間で一度もない。

で、実際に frontmatter が欠けたファイルが1件、2ヶ月半見逃されていた。
CIは毎回それを検出して、ログに を出して、成功で終わっていた。

なぜ僕は疑わなかったのか

ここが本題で、3つあると思っている。

1. 報告の形が整っていたから。

このCIにはエラーカウンタがあるし、違反したファイル名も出すし、成功と失敗で分岐もしてるし、警告の絵文字まで付いている
 →って、ソースを読んで確かめたわけじゃない
 →作ってもらったときに AI がそう報告してきて、実際ログにもそのとおり出ていた

で、報告は嘘じゃなくて、カウンタも分岐も警告も本当に全部ある。
なかったのは exit 1 だけ。
やってることの列挙が全部本当でも、効いてるかどうかは別だったんだよね。

報告の形が整ってると、人間は「ちゃんとしてそう」で受け取っちゃうんだよね、それも5秒で。
効いてるかどうかは報告からは分からなくて、失敗するかどうかでしか分からない。

AIが作ったものはここが厄介で、形と報告だけは絶対に崩さない。
だから形で受け取るのが一番危ない
 →人間が手を抜いた仕事は、コードも報告もどこか崩れるから一応引っかかる
 →AIは崩れない

2. ずっと成功していたから。

当たり前だけど、書いておく価値があって、成功が続いてるから問題ないんだと5ヶ月思ってた。
でもCIの「成功」って、落ちる条件をひとつも書いてなくても出るから、検査して問題なしなのか、そもそも検査してないのか、結果だけじゃ区別がつかない。

3. 「ログには警告が出てます」で満足したから。

これがいちばん厄介で、このCI、違反を検出して、ファイル名を出して、警告もして、仕事はしてたんだよね。
でも、成功のログなんて普通見ないよね。笑

あ、だから「見逃した」っていうのも正確じゃないな、出したって見てないんだから、見逃すも何もない。
「ログに出す」がゴールになってる設計を受け取った時点で、こうなるのは決まってた。
ログって、落ちて通知が来て初めて開くものだし。

受け取るときに聞くべき質問は、ひとつだけだった。
これ、本当に落ちるのか?
聞いてなかったわ・・・

さらに悪いこと。途中で一度、手を入れている

約4ヶ月目に、この workflow を触っている。

理由は「Actions の分数を食いすぎている」。
Claude に頼んで、リンクチェックを週次化して、concurrency で二重実行を止めた。
分数は減ったし、効果はあった。

でも触ったのはリンクチェックのほうだけで、push ごとに走る frontmatter チェック(なにも検知してない側)は、そのまま残した。

「消費が多い」ほうは治って、「なにも検知してない」ほうはそのまま残った。
しかも「CIは見直した」という記憶だけが残るので、次に疑う機会まで遠のく。

これが一番の教訓だと思ってて、コストが気になって触ってるときは、コストしか見てない。
触るついでに「これは何を検出してるのか」「検出したら何が起きるのか」を1回声に出せばよかったんだけど。

どう直したか

これも僕が手を動かしたわけじゃなくて、Claude と壁打ちして決めた。
CI を直すんじゃなくて3層に割り直すことにして、コードは Claude が書いた。

ちなみに hook の存在自体は前から知ってて、ちゃんと理解してなくて使えてなかっただけ。
使えるようになった起点は会社のAI勉強会
 →若手と話していて「仕様駆動開発」「コンテキストエンジニアリング」「ループエンジニアリング」という3つの言葉が出てきて、自分がやってることとの対比を考えた方がいいなと思った

その日のうちに Claude と調べて自分のリポに当てはめて、「文書のお願いを hook の保証に変える」で6本まとめて入れて、調べた結果はペライチにしてメンバーにも配った。
今回の frontmatter チェックは、その延長の12本目
 →若手と同じ目線で勉強会に混ざって、拾った言葉をスキマ時間で調べて自分の頭に落として、まとめて共有する
 →こういうことをするのが大事なんだと思う、僕らの層として

だから今回のは、勉強会の流れで学んだ仕組みを、この事故にも当てただけ。

1. 検証は手元の hook に移す

frontmatter チェックは Node のスクリプトにして、Claude Code の PostToolUse hook から呼ぶようにした。
ファイルを書いた直後に判定して、違反なら decision: "block" を返す。
要点だけ抜粋するとこう。

// 判定は開始 --- と閉じ --- の有無だけ。小さく単純に
function violationOf(absPath) {
  const lines = fs.readFileSync(absPath, 'utf8').split(/\r?\n/);
  if (lines[0] !== '---') return 'frontmatter が無い(1行目が --- でない)';
  for (let i = 1; i < lines.length; i++) {
    if (lines[i] === '---') return null; // 閉じがあれば OK
  }
  return 'frontmatter が閉じていない(閉じ --- が無い)';
}

// 違反なら echo じゃなく block を返す。書いた本人(AI)の手が止まる
process.stdout.write(JSON.stringify({
  decision: 'block',
  reason: `[frontmatter] ${relPosix}: ${why} — 先頭に --- で囲んだ frontmatter を追加すること`,
}) + '\n');

CIと比べた利点は3つ。

  • 速い

     →push を待たない

  • その場で直せる

     →AIが自分で直す
      →書き込み自体は成功してるので実害もない

  • exit の書き忘れが起きにくい

     →hook はブロックが仕事なので、返り値を書き忘れたら動かないことにすぐ気づく

2. 網は週次スイープに残す

hook はローカルのセッションしか通らない
 →Web版やスマホ経由の書き込みは素通りする

だから同じスクリプトを --all で全件走らせる週次 workflow を別に用意して、こっちは違反があれば exit 1 する(今度こそ)。

役割分担としては、hook が「速いけど漏れる」側で、週次スイープが「遅いけど漏れない」側。

3. CIには「落ちて初めて気づけるもの」だけ残す

CIは外部リンク切れチェック専用にして、continue-on-error を外した。
workflow のコメントにはこう書いた。

# continue-on-error は付けない: 落ちて初めて気づける(Actions の失敗通知が唯一の導線)。

通知が来ないチェックは、無いのと同じだった。
成功のログは誰も読まない(僕は5ヶ月読まなかった)から、エラーで落ちる以外に伝わる道がない。

承認する側として、次から何を見るか

自分のリポの話として書いてきたけど、たぶんこれから増えるのは、部下が「AIに書かせました」と持ってきたものを通すかどうかの場面だと思っている。
僕が家でやった素通しを、今度は仕事でやることになる。
同じ罠にかかる、形は整ってるから。

だから、コードの中身を読むのはやめて、聞くことを3つに決めた。

  1. これは何を検出するのか

     →一言で言えないなら、たぶんまだ設計されてない

  2. 検出したら何が起きるのか

     →「ログに出ます」は答えになってない
      →誰の目に、どういう形で入るのか

  3. わざと違反させたのを見たか

     →見てなければ、そのチェックは未検証

3つとも、成果物を読まずに聞ける。
僕らの層は時間的にもブランク的にも、ソースを読み切れるかが微妙なラインだし、読んだら読んだで形に引っかかるから、読まずに聞く。

差し戻すときも「コードが悪い」じゃなくて「わざと落として見せて」で済むし、AIが書いたかどうかに関係なく効くから、言い方として揉めない。

というかこれ、書いてて気づいたけど、新しい話じゃなかった。
決済系のシステムを作るとき、パトライトのマニュアル(一般的にはエラー対応マニュアルかな)を書くのとずっと同じだ
 →何を検知したいのか
 →本当にエラーとして検知する必要があるのか
  →リトライじゃだめなのか、再操作じゃだめなのか
 →検知して何をしたいのか、してほしいのか
 →誰がやるのか、できるのか
 →システムの特性・規模・仕組みに合わせて考えて、必ず試験で「それを検知して対応する」ところまで試験する

で、これを必ず開発してるみんなと会話して決める。
お互いの意図を理解し合って、同じ目線で話し合って、最後は僕が覚悟する。
レビューというより認識合わせ。
僕がコードを読めなくても仕事になってたのは、たぶんこれをやってたからだ。

AIが相手になって、この会話を省略した、それが今回の事故なんだと思う。

教訓(自分用のメモ)

  1. echo するだけのチェックは、チェックではない

     →exit code を返さない検証は、検証してるように見えるだけ

  2. AIが作ったものは、形と報告が整っている。だから受け取ってしまう

     →カウンタも分岐も警告文も、本当にある
     →形と効力は別物

  3. CIを入れたら、一度わざと違反を作って、ちゃんとエラーになるのを見る

     →僕はこれをやってなくて、1分で済むのに5ヶ月失った

  4. continue-on-error はチェックを飾りに変える

     →付けるなら「落ちても構わない理由」をコメントに残す

今日できること(1分)

自分のCIで、わざと違反を1件作って push してみる。
ちゃんとエラーになるか。
 →yml が読めなくても、AI に「このチェックにわざと引っかかる違反を作って」と頼めばできる

エラーにならなかったら、そのチェックは動いてないから、exit 1 を1行足そう。

僕はそれをやらないまま5ヶ月払い続けて、266分、何の役にも立ってなかった。笑

0
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
0
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?