「まだ月の半ばなのに、Actions の枠がもう残っていない」。ある Free プランの org で、10月13日の時点で GitHub Actions の月 2,000 分の枠を 1,893 分使っていた。13日で約95%、残り18日を107分でしのぐ計算になる(月末まで CI を回すなと言われているに等しい)。
枠の7割強は1つの repo が使っていた。では、テストを削るしかないのか? 実際は、job 単位で数え直すと目立ったのは「テストを走らせるか決める仕事」と「同じ変更をもう一度確かめる仕事」だった。その2つを減らす3つの手を、実測量・副作用・向く条件で比べる。
枠を食うのは「回数」と「切り上げ」
Actions の分数は job ごとに数える。公式の料金表のとおり、各 job の時間は分単位に切り上げる(Actions runner pricing)。8秒で終わる job なのに1分、61秒なら2分だ。タクシーの初乗りと同じで、短い乗車ほど割高になる。
API で job ごとに集計し直すと(10月1日〜13日)、大きな無駄は3つあった。
| 無駄 | 何が起きていたか | 13日の消費 | 実働 |
|---|---|---|---|
| 変更判定 job | 変わったパッケージを調べるだけの十数秒の job | 179分(164回) | 36分 |
| merge 後の再実行 | PR で検証済みの変更を、統合ブランチ(dev)でもう一度 CI | 140分(うち51分は変更判定 job) | 77分 |
| デプロイ判定 job | CI が終わるたびに、デプロイするかを判定 | 68分(68回、うち13分は本番の判定で残る) | 8分 |
変更判定 job は実働の5倍、デプロイ判定 job は9倍近くで数えられていた。テストを飛ばして節約するための job のはずが、節約した分を食っていた。重なりを除くと3つで336分、この repo の消費のおよそ4分の1になる。
3つの手を比べる
実測量の大きい順。削れるのは、その部分が使っていた分の見込みだ。
| 変更判定 job を paths に寄せる | merge 後の再実行をやめる | デプロイの起動元を絞る | |
|---|---|---|---|
| 変える場所 | paths で起動を絞り、パッケージ別の判定は test job の step へ | push のトリガーから統合ブランチを外す(main は残す) | branches を main だけにし、本番の判定に push の条件を足す。staging は push で直接起動 |
| 副作用 | 起動しなかった check が Pending のまま残る | merge 後の合成状態と直接 push は CI を通らない | staging が CI の成功を待たない |
| 向く条件 | 必須チェックを使っていない | 統合ブランチに PR 経由でしか入らない | 同左 |
変更判定 job を paths に寄せる
「関係するファイルが変わったか」は paths で書け、関係ない変更では workflow 自体が起動しない。「どのパッケージが変わったか」は test job 内の step にすれば job が増えない。入れ忘れやすいのは、ルートの package.json・lockfile・workflow 自身・Dockerfile など、パッケージに属さないファイルだ。
必須チェックの workflow を paths で起動させないと、check が Pending のまま残る。PR は merge できない(workflow 構文の公式説明)。job の if で飛ばせば Success 扱いになる(status checks の公式説明)が、パスで判定するにはまた判定 job が要る。だから必須チェックを使う repo(Team 以上。Free の private repo では保護ブランチ自体が使えない。プラン一覧)は、対象の workflow には paths を付けない。
merge 後の再実行をやめる
PR の CI は、base に merge した結果(refs/pull/<番号>/merge)を検証している(イベントの公式説明)。base が動いていなければ、merge 後の再実行は同じ中身を見るだけだ。ただ、先に別の PR が merge されていると、合わせた状態はどちらの CI も見ていない。だから main への push の CI は本番の手前のゲートとして残す。
デプロイの起動元を絞る
workflow_run の branches は、起動元の run の head ブランチで絞り、起動元のイベントは見ない。PR の CI の head は PR を出した側のブランチだ。branches に統合ブランチを入れていると、統合ブランチから main への PR の CI が終わるたびにデプロイ判定が走る。この org では13日で10回走り、うち4回は push のときにデプロイ済みの commit を staging へ出し直していた。
branches は main だけにする。それでも head が main の PR では起動しうるので、本番の判定に github.event.workflow_run.event == 'push' を足す。staging は統合ブランチへの push で直接起動させれば、staging 側の判定 job は要らない。
変えた後に取りこぼすもの
| タイミング・経路 | 何が起きるか | 確かめ方 |
|---|---|---|
| merge した直後 | PR では push と workflow_run のトリガーが動かない(workflow_run は既定ブランチの定義でのみ起動) | merge 後の最初の push と次の main への merge で run を見る |
| ルートのファイルだけの変更 | paths に入れ忘れると CI が起動しない | lockfile だけを変えた PR で CI が起動するか見る |
| 3,000 ファイルを超える変更 | 一致するファイルが差分の先頭3,000件に無いと起動しない | 一括変更の PR では CI の起動を目で確かめる |
| 統合ブランチへの直接 push | CI を通らずに staging へ出る | 直接 push しない運用を決める |
AI エージェントに調べさせるときの指示
「CI の分数を減らして」とだけ頼むと、エージェントは実働の長いテストやビルドから削りがちだ。膨らむのは短い job なので、まず切り上げ後の分で測らせる。次のコマンドは直近50回の run の job を、切り上げた分・実働・回数で並べる(gh と jq が要る)。1行目の owner/repo を、調べる repo に書き換えて貼る。
REPO=owner/repo
gh api "repos/$REPO/actions/runs?per_page=50" \
--jq '.workflow_runs[] | [.id, .name, .event] | @tsv' |
while IFS=$'\t' read -r id wf ev; do
gh api "repos/$REPO/actions/runs/$id/jobs?filter=all&per_page=100" |
jq -r --arg wf "$wf" --arg ev "$ev" '.jobs[]
| select(.conclusion != "skipped" and .started_at != null and .completed_at != null)
| [$wf, $ev, .name, ((.completed_at|fromdate) - (.started_at|fromdate))] | @tsv'
done |
awk -F'\t' '$4 < 0 { next }
{ k = $1 " / " $2 " / " $3; t[k] += int(($4 + 59) / 60); s[k] += $4; n[k]++ }
END { for (k in t) printf "%5d 分(実働 %6.1f 分)%3d 回 %s\n", t[k], s[k] / 60, n[k], k }' |
sort -rn
公開 repo で試すと、数秒の job が「2 分(実働 0.1 分) 2 回」のように並ぶ。測った後の指示は次のとおり。
GitHub Actions の消費を減らしたい。変更の前に次を報告してください。
- job ごとに切り上げ後の分・実働・回数を集計し、切り上げ後の分と実働の差が大きい job
- 同じ commit を CI で何回検証しているか
- デプロイ用 workflow が PR の CI 完了でも起動するか
- 必須チェックにしている workflow
変更では次を守ってください。
- paths にはルートの package.json・lockfile・workflow 自身・Dockerfile も入れる
- 必須チェックの workflow は paths で飛ばさず、本番の手前の CI は残す
- actionlint で確かめ、merge 後に最初に起動するはずの run と確認項目を挙げる
削る前に検討したい別案
- 払う:超過分は Linux 2コアで1分 $0.006(Actions の課金の説明)。13日で1,893分のペースなら月約4,500分で、支払い方法を登録すれば月15ドル前後(試算)で回る
- 公開する:公開 repo の標準ランナーは無料
まとめ
Actions の消費は、実働ではなく「job の数 × 切り上げた分」で決まる。
あなたの repo でも、まず切り上げ後の分で job を並べてみると、テストより先に削れるものが見つかるはずだ。数秒の job ほど、請求書の上では長生きしている。



