個人で運営しているサイトが20個ほどあって、リポジトリは全部 private だ。デプロイは GitHub Actions で、main に push すると Cloudflare Workers に出る。

Free プランの Actions は 月2,000分が上限だ。使い切ったとき、支出上限を $0 のままにしていればデプロイが止まる。上げていれば $0.008/分の従量課金になる。どちらも避けたい。

ここから、いくつかの判断をした。そのうち2つは、あとで根拠が崩れた。

決定1: CI ではテストを回さない

2026年9月1日に決めた。全サービスで、GitHub Actions ではテスト(E2E・ユニット)を実行しない。テストはローカルで push 前に通す。

このとき書いた根拠はこうだった。

  • 8月の使用量は概算 約990分(tools 279・blog 206・books 94・pets 94・shiritori 74・kaomoji 70 …)
  • E2E は1回2〜4分(ブラウザ導入 + build + テスト)。E2E を持つ7サービスに CI で足すと月 +500〜700分で上限に近づく
  • 静的サイトが多く、壊れ方の大半はビルド時(型・データ検証)に落ちる。E2E が守るのはクライアント JS の一部
  • E2E は揺らぎで偽の赤が出る。deploy のゲートにすると本番反映が止まる

同日、blog の E2E 専用ワークフローを撤去し、holiday と shiritori の deploy からテストの段を外した。残りのサービスはもともと CI でテストを回していなかった。

訂正1: その数字は根拠になっていなかった

翌日、決定の記録を再監査した。背景の数字が方針の根拠になっていないと分かった。

  • 「E2E を足すと上限に近づく」→ この文書自身の数字でも 990 + 700 = 1,690分 < 2,000分。上限には届かない
  • 最大の消費源 tools の279分は、日次ワークフローに入っていた sleep 480(毎日8分 ≒ 月240〜310分)でほぼ説明できる。テストとは無関係の待ち時間で、tools はもともと CI でテストを回していない

つまり「テストを外せば枠が守れる」は、数字の上では成り立っていなかった。本当に効くのは sleep 480 を短くすることだった。

方針そのものは維持した。「無料枠を deploy 用に温存したい」という判断は、数字の補強が無くても成り立つ。ただ、根拠として書いた数字は間違っていたので、記録に訂正を足した。決めた翌日に根拠を訂正することになったが、間違った根拠を残すほうが後で困る。

決定2(一度は): Workers Builds に移す

9月に入って、枠の逼迫が現実味を帯びた。AI に相談したら「blog を Cloudflare の Workers Builds に繋いで、Actions から外す」という案が出てきた。Workers Builds は Cloudflare 側でビルドとデプロイをやってくれるので、Actions の分数を使わない。理屈は通っている。

そこで聞き返した。「GitHub のやつでビルドできているのに、なぜ Workers Builds にも繋ぐのか」。

この問いで、数字を取り直すことになった。そして前提が崩れた。

訂正2: 主因は仕組みではなく、push の回数だった

取り直した数字はこうだ。

  1. blog は Actions から離れられない。 予約投稿のために毎日0:15 JST の定時リビルドが要る。Workers Builds には cron が無い。 移行しても Actions の cron は残るので、枠の問題は解けない
  2. 主因は push 回数だった。 blog の9月は、定時ビルドが1日1回で約4分。それに対して push は 9月6日だけで24コミット・20デプロイ = 約84分。定時ビルドの20日分を1日で使っている。移行しても push の癖が残れば、消費先が変わるだけだ
  3. 繋いだあとに静かに壊れる実例があった。 別のサービスで Workers Builds 側のビルドが失敗し続けていたが、誰も気づいていなかった

さらに、Workers Builds は API では触れない。API トークンの権限カタログに Workers Builds のグループが無く、Git 連携の作成はダッシュボード専用だ。Terraform で管理している構成の外に、手作業でしか触れないものを増やすことになる。

移行はやめた。代わりに Actions 側の設定で消費を抑え、push の回数そのものを減らすことにした。

入れた対策

9リポジトリに同じ設定を入れた。

cancel-in-progress で、連続 push は最新の1本だけ完走させる。

concurrency:
  group: deploy
  cancel-in-progress: ${{ github.event_name == 'push' }}

常に新しいほうが後に出るので、上書き事故は起きない。ただし条件を付けてある。定時ビルド(schedule)は打ち切らない。 blog の定時ビルドが途中で止まると、その日の予約記事が公開されない。github.event_name == 'push' で push のときだけ打ち切るようにした。

paths-ignore で、デプロイに関係ないファイルの変更ではビルドを起こさない。

on:
  push:
    branches: [main]
    paths-ignore:
      - ".github/**"
      - "*.md"
      - "docs/**"
      - "e2e/**"

.github/** を入れてあるので、この設定変更自体はデプロイを起こさなかった。

運用: 1リポジトリ、1セッション、1 push に寄せる。 仕組みで抑えられるのはここまでで、あとは push する側の癖だ。細かく push するのをやめ、まとめてから出す。

付随して、3つのサービスが Actions と Workers Builds の二重デプロイになっていたことも分かった。これは Actions 側を外した。移行ではなく、重複の解消だ。

次に逼迫したら

cron を持たないサイトから Workers Builds に寄せる。blog のように定時ビルドが要るものは Actions の cron を残すか、自宅の開発機のタイマーから叩く形にする。今のところ、そこまでは要らない。

この件で分かったこと

  • 無料枠の逼迫は、まず内訳を取る。 「テストのせい」「仕組みのせい」と思ったものは、どちらも数字を取ったら違った。実態は sleep 480 と push 回数だった
  • 決めた根拠が間違っていたら、方針を残すとしても根拠は訂正する。 間違った根拠が記録に残ると、次に同じ判断をするとき、同じ間違いを引く
  • AI の提案は、一つ聞き返すと崩れることがある。 「なぜ二重に繋ぐのか」で十分だった。提案の筋が通っているかではなく、その提案が解こうとしている問題が本当にあるかを確かめる
  • cron の有無は、移行の可否を決める。 ビルドの仕組みを変える前に、schedule を使っているワークフローが無いか見る
  • cancel-in-progress は schedule に効かせない。 定時の処理を途中で止めると、その日の分が出ない
  • 一番効く対策は、たいてい仕組みではなく行動のほうにある。 push を減らす、が答えだった

月2,000分は、使い方を間違えなければ足りる。足りなくなったとき、最初に疑うべきは枠の大きさではなく、自分の使い方だった。