ci(tests): composer audit の exit code を分類し、通知が理由を出し分けるようにする - #797
Conversation
2026-08-31 の週次 schedule(run 33367156425)が packagist の HTTP/2 502 で 落ちた。`composer audit` は advisory の取得に失敗して exit 100 を返していたが、 ステップが `run: composer audit --no-interaction` の素の形だったため、 「advisory を検出した(exit 1)」と同じ赤に潰れていた。 同日に #795(notify-failure)が入ったので、次に 502 で落ちると 「🔴 週次 CI が失敗しています」という固定文言の Issue が自動起票される。 読み手には脆弱性の検出としか読めない。原因の区別は run のログを開かないと つかず、Issue の側には何も伝わらない。 ## 実測(composer 2.9.8) | 事象 | exit | |---|---| | advisory を検出 | 1 | | advisory を取得できない(到達不能 proxy で再現) | 100 | 区別は composer が最初から返していた。ステップがそれを捨てていただけ。 ## 変更 - `unit` ジョブに `outputs.audit_status` を生やす(clean / advisory_found / upstream_unavailable)。ジョブ間なので $GITHUB_ENV では届かない。 - audit ステップは出力を書いてから exit する。exit 100 は `::error::` で 「監査は実行されていない・脆弱性の検出ではない」と明示して、赤のまま落とす。 - `notify-failure` は題名と本文を出し分ける。上流障害は 🟠 の別題名にしたので dedup が題名ごとに効き、上流障害と本物が同じスレッドに混ざらない。⚠️ 上流障害と断定するのは runtime-smoke が緑のときだけ。両方落ちていれば 通常の赤へ倒す(「脆弱性ではありません」と言い切らない)。 ## 採らなかった選択肢 - `--ignore-unreachable`: 取得失敗が exit 0 になる。「advisory が無い」と 「確かめられなかった」が同じ緑に潰れ、このステップを独立させた理由 (走ったかどうかが判ること・#792)をそれ自身で無効化する。 - `--locked`: CI は直前に composer install を通すので installed == lock。 installed を測る方が実際に動く木に近い。逆に手元で叩くときは --locked が 要る(vendor が古いと解消済みの CVE が再燃して見える)。理由をコメントに残した。 ## リトライについて(過大評価しないこと) 0/60/300 秒の3回を入れたが、短い瞬断のための保険にすぎない。 2026-08-31 の障害は実測 26分49秒(NeNe 失敗 07:09:48Z → nene-vault の Weekly audit success 07:36:37Z)で、この約6分では救えなかった。 効くのはリトライではなく分類のほう。 ## 検証 audit ステップ 4 パス(0 / 1 / 100×3 / 100→復旧)と notify-failure 4 パス (上流障害・advisory 検出・上流障害だが smoke も赤・出力が空)を composer と gh のスタブで実走。dedup も題名ごとに別 Issue へ落ちることを確認。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019g1VeX1kq7Mfxu3qDRxuQN
#795 の simulate_failure は Setup PHP より前で exit 1 するので audit ステップに 到達しない。=分類も outputs も動かず、「advisory を取得できなかった」通知は 一度も実行されないまま入ることになる。 simulate_audit_exit(dispatch input)を足し、audit ステップが値を受けたら composer を呼ばずにその終了コードを返す。リトライも回さない(60+300 秒待つ 意味がないため)。これで 分類 → outputs → notify-failure の伝播までを 実 CI で1回通せる。 🔴 notify-failure の if も同時に広げた。audit ステップだけが input を読む形に すると、リハーサルは分類までしか届かず、**検証したい伝播(needs.unit.outputs)が 一度も評価されない**。条件は github.event_name == 'workflow_dispatch' で 明示的に守り、schedule 時に inputs が null になる経路と混ざらないようにした。 検証: simulate_audit_exit = 100 / 1 / 0 の3パスと、空(=通常どおり composer を 呼ぶ)をスタブで実走。それぞれ status=upstream_unavailable / advisory_found / clean と job の終了コードが期待どおりになることを確認。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019g1VeX1kq7Mfxu3qDRxuQN
追記: 🟠 パスのリハーサル口を足しました(
|
✅ 🟠 パスを実 CI で実測しました — 未検証だった伝播が潰れましたhub がリハーサルを実行(
→ Issue #798 が これで測れたこと
この PR で未測定として残っているものはありません。 |
#795 / #797 が入って main が動いたのに、両文書は 2026-08-20 のままだった。 session-state.md は自分を運んだコミット(#794)より古い状態を書いており (`main` = `4d9bd68` / PR #793 の DoD が「未実施」)、実際には workflow_dispatch はマージ 37 秒後に success していた。 ## session-state.md(全面更新) 現況を再実測して置き換え(commit 1,071 / class 311本 69,972行 / テスト 5,249メソッド 315ファイル / リモート枝 351本、いずれも 23:29 JST 実測)。 判定は 08-20 から変わらず「休眠だが健全」。 次に触る人が踏む地雷を、根拠つきで残した: - `--ignore-unreachable` を使わない理由(緑にせず赤のまま理由を分ける) - CI では `--locked` を付けず、手元では必ず付ける理由 (素の audit は vendor を見るので古い作業木だと解消済み CVE が再燃して見える。 実際にこのセッションで踏みかけた: lock=v5.8.4 / vendor=v5.8.0) - リハーサル口を足すときは notify-failure の if も同時に広げること (さもないと伝播が一度も評価されず「口を足したのに目的が達成されない」) - 終了コードを測るときパイプを挟まない(`| tail` は tail の $? を拾う) - スタブで通ったことを実 CI の証拠に読み替えない - squash マージでは `--is-ancestor` / `branch -d` が必ず「未マージ」と言う 🔴 マージの打鍵者の記録も正した。GitHub 上の `mergedBy` は `hideyukiMORI` だが、 `gh` が施主のトークンで動くためトークンの持ち主を指すだけで打鍵者は区別できない。 正確には「施主承認のうえ hub がマージ」。 ## docs/todo/current.md 2026-08 節に 08-31 の項を追加。事故(packagist 502)と、層が2つあったこと (誰にも伝わらない / 伝わっても内容が誤り)、採らなかった選択肢とその理由、 リトライが 26分49秒の障害を救えないこと、実 CI での検証結果を記録。 08-20 の項の 60日しきい値には supersede 注記を付けた(活動再開で 10月下旬へ後退)。 Claude-Session: https://claude.ai/code/session_019g1VeX1kq7Mfxu3qDRxuQN Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
休眠中の CI が初めて壊れた日の記録。コードは1行も動いておらず (class 69,972 行・テスト 5,249 メソッドは 08-20 から不変)、動いたのは CI の通知経路だけ。マージは #795 / #797 / #799 の3本。 「週次で advisory を検知する」という装置が、実は3層のどこでも成立して いなかったことを記録した: ① 失敗が誰にも伝わらない(#795 が10日 open で PR ブランチにしか無かった) ② 伝わっても内容が誤り(素の composer audit が exit 1 と 100 を潰していた) ③ その誤りを検証する口が無い(simulate_failure は audit に到達しない) 技術的知見を9点、根拠と実測値つきで残した。とくに次に踏まれやすいもの: - --ignore-unreachable を使わない理由 - composer audit の既定対象は lock ではなく vendor(手元では --locked) - リハーサル口は本番経路の手前で止まりやすい - 終了コードを測るときパイプを挟まない - squash では --is-ancestor が必ず「未マージ」と言う - gh の mergedBy は打鍵者ではなくトークンの持ち主 前回(2026-05-30)からの繰り越しも棚卸しした。Node.js 20 非推奨は状況が 変わっており(すでに Node 24 で走行・実害なし)、繰り越しとして閉じてよい。 Claude-Session: https://claude.ai/code/session_019g1VeX1kq7Mfxu3qDRxuQN Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
本日の週次 schedule(run 33367156425)は packagist の HTTP/2 502 で落ちました。
composer auditは advisory の取得に失敗してexit 100を返していましたが、ステップが素のrun: composer audit --no-interactionだったため、「advisory を検出した(exit 1)」と同じ赤に潰れていました。同日に #795 が入ったので、次に 502 で落ちると固定文言の Issue(
🔴 NeNe: 週次 CI(schedule)が失敗しています)が自動起票されます。読み手には脆弱性の検出としか読めません。区別は最初から在った
Found 2 security vulnerability advisories affecting 1 packageProcess completed with exit code 100composer 2.9.8 で実走。ステップがそれを捨てていただけです。
変更
unitにoutputs.audit_status(clean/advisory_found/upstream_unavailable)を生やす。ジョブ間なので$GITHUB_ENVでは届きません。100は::error::で「監査は実行されていない・脆弱性の検出ではない」と明示し、赤のまま落とします。notify-failureが題名と本文を出し分け。上流障害は 🟠 の別題名にしたので dedup が題名ごとに効き、上流障害と本物が同じスレッドに混ざりません。採らなかった選択肢
--ignore-unreachable: 取得失敗がexit 0になります。「advisory が無い」と「確かめられなかった」が同じ緑に潰れ、このステップを独立させた理由(走ったかどうかが判ること・ci: tests.yml に composer audit ステップを追加(追加前 0 件を実測・フリート【PHP-AUDIT】の最後の1艦) (#791) #792)をそれ自身で無効化します。--locked: CI は直前にcomposer installを通すので installed == lock。installed を測る方が実際に動く木に近い。--lockedが要ります — 素の audit は installed を見るので、vendor/が古いと解消済みの CVE が再燃して見えます(本 PR の調査中に実際に踏みかけました: lock=v5.8.4なのに手元の vendor=v5.8.0で smarty の CVE 2件が出た)。理由はコメントに残しました。0/60/300秒の3回を入れましたが短い瞬断の保険にすぎません。本日の障害は実測 26分49秒(NeNe 失敗07:09:48Z→ nene-vault の Weekly audit success07:36:37Z)で、この約6分では救えませんでした。効くのはリトライではなく分類のほうです。検証(スタブで実走・8パス)
audit 4パス(
0/1/100×3 /100→復旧)+ notify 4パス(上流障害 / advisory 検出 / 上流障害だが smoke も赤 / 出力が空)。dedup も題名ごとに別 Issue へ落ちることを確認済み。Ref: #795 / #792 / #793