GitHub Actions の課金分を実測ベースで削る
Actions の請求を推測でなく実測から削る手順。ジョブ単位の分切り上げという課金構造、ステップ別に時間を割る API の叩き方、短時間高頻度は統合・長時間は per-row 往復の一括化という打ち分け、並列度を上げても効かないケースの見分け方、そして『遅いステップが実は同じデータを毎回処理し直している欠陥』の見つけ方まで。本番の夜間ジョブを実測目的で再実行して重複レコードを大量生成した失敗例と、その巻き戻しの作法も含む。
約10.5万トークンの節約 (API料金換算で約160円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。
この巻物について
「GitHub Actions の課金分を実測ベースで削る」は、開発プロセスカテゴリのAI指示書(MDファイル)です。Actions の請求を推測でなく実測から削る手順。ジョブ単位の分切り上げという課金構造、ステップ別に時間を割る API の叩き方、短時間高頻度は統合・長時間は per-row 往復の一括化という打ち分け、並列度を上げても効かないケースの見分け方、そして『遅いステップが実は同じデータを毎回処理し直している欠陥』の見つけ方まで。本番の夜間ジョブを実測目的で再実行して重複レコードを大量生成した失敗例と、その巻き戻しの作法も含む。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約10.5万トークン(API料金換算で約160円)・88%のトークンを節約できます。
- カテゴリ
- 開発プロセス
- 対応AI
- claude-code、cursor、codex-cli
- ライセンス
- 商用利用可 (再販不可)
- 価格
- 無料
- ゼロから開発時
- 約12万トークン
- この巻物使用時
- 約1.5万トークン
- 節約量
- 約10.5万トークン (約160円)
- 更新日
- 2026-08-27
使い方 (AIに渡す3つの方法)
いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/github-actions/raw を読み込んで、この指示書どおりに実装して"
中身
GitHub Actions の課金分を実測ベースで削る
CI/cron を GitHub Actions で回していて「Actions の請求が思ったより高い」ときに、 推測せず実測から削るための手順書。AI エージェントにそのまま読ませて実行できる粒度で書いてある。
0. 前提: 課金は「ジョブごとの分単位切り上げ」
GitHub Actions は ジョブ単位で実行時間を分に切り上げて課金する。つまり:
- 8秒で終わるジョブを15分ごとに回すと、1分 × 実行回数 が課金される(実計算3時間でも月700分超)
- 逆に 61秒のジョブは2分課金。60秒を切れれば一撃で半額になる
だから削り方は2種類しかない。
| 状況 | 打ち手 |
|---|---|
| 短時間(〜30秒)× 高頻度 | 他のワークフローにステップとして統合して起動オーバーヘッドを1回に集約 |
| 長時間(数分〜数十分) | 中身の実行時間そのものを縮める |
1. まず実測する(推測で着手しない)
1-1. ワークフロー別の課金分を出す
REPO=<owner>/<repo>
gh api "repos/$REPO/actions/workflows" --jq '.workflows[]|"\(.id)\t\(.name)\t\(.path)"'
各ワークフローの1回あたり実行時間は /timing エンドポイントでしか取れない(一覧APIには無い)。
for id in $(gh api "repos/$REPO/actions/workflows/<file>.yml/runs?event=schedule&per_page=10" --jq '.workflow_runs[].id'); do
d=$(gh api "repos/$REPO/actions/runs/$id/timing" --jq '.run_duration_ms')
echo "$id $((d/1000))s"
done
billable.<OS>.total_msは 0 を返すことがあるのでrun_duration_msを使う- 10秒未満の run は集計から除外する(課金ブロックなどでジョブ自体が起動していない可能性)
- 月の課金分 ≈
ceil(1回の秒数/60) × 月の実行回数
1-2. 遅いジョブは「ステップ別」に割る
ここが要。ジョブ全体の秒数だけ見ても打ち手が決まらない。
gh api "repos/$REPO/actions/runs/<run_id>/jobs" \
--jq '.jobs[]|(.steps[]|"\(.name)\t\(.started_at)\t\(.completed_at)")' \
| while IFS=$'\t' read -r n s e; do
ss=$(date -u -d "$s" +%s); ee=$(date -u -d "$e" +%s); echo -e "$((ee-ss))s\t$n"
done
これで「40分のうち13分は A、8分は B」と割れる。削るのは上位2つだけでいい。
2. 短時間・高頻度は「統合」する
8秒のジョブを独立ワークフローで15分ごとに回すのは、課金上いちばん損。 既存ワークフローにステップとして足す。
統合先は「cron 式」ではなく「実発火時刻」で選ぶ
GitHub は schedule を大量に間引くので、cron 式と実発火は一致しない。実測例:
| cron 式 | 実発火 |
|---|---|
*/15 * * * * | 41回/日・間隔ランダム(最大90分空く)・分は事実上ランダム |
0 * * * * | 22〜23回/日 |
*/15 の実発火の「分」はバラけるので、
「15分毎のジョブの中で [ $(date +%-M) -ge 15 ] && exit 0 して hourly を維持」は成立しない(23回/日 → 約10回/日に落ちる)。
「直近1時間だけ集計する」系のステップだと、落ちた回のデータは二度と処理されない=欠損になる。
実発火時刻はこれで測る:
gh api "repos/$REPO/actions/workflows/<file>.yml/runs?event=schedule&created=<YYYY-MM-DD>&per_page=100" \
--jq '.workflow_runs[].created_at'
→ 統合先は cron 式が完全一致し、実発火回数もほぼ同じワークフローを選ぶ。
統合したら旧ファイルは消さず workflow_dispatch だけ残す
schedule: を外して手動実行だけ残す。手で叩ける経路を消さない。
落とし穴: 前ステップの失敗で後続が silent skip される
複数の処理を1ジョブに統合すると、前のステップが失敗した時点で後続が黙ってスキップされる。 統合するなら全ステップに付ける:
- name: <step>
if: ${{ !cancelled() }}
3. 長時間ジョブは「per-row の往復」を疑う
実測でいちばん多いのがこれ。数千行を1件ずつ API/DB に投げている。
3-1. 1件ずつの upsert → 配列でまとめる
効果が最も大きく、リスクが最も低い。実測例: 3,719件の per-row upsert = 486秒 → 7秒。
// before: 1件ずつ(3,719回の往復)
for (const row of rows) {
await db.from("table").upsert(row, { onConflict: "key" });
}
// after: 500件チャンクの配列 upsert
for (let i = 0; i < rows.length; i += 500) {
const chunk = rows.slice(i, i + 500);
const { error } = await db.from("table").upsert(chunk, { onConflict: "key" });
if (!error) { ok += chunk.length; continue; }
// 重要: チャンクが失敗した時だけ1件ずつ再試行する。
// 「1件の不正データで全体を落とさない」という元の挙動を必ず保つ。
for (const row of chunk) {
const { error: e } = await db.from("table").upsert(row, { onConflict: "key" });
if (e) { console.warn(`upsert error ${row.key}: ${e.message}`); continue; }
ok++;
}
}
同様に「1件ずつ update して無効化」も .in("id", ids) の1回にまとめる。
3-2. ページングには必ず order を付ける
range/offset によるページングは並び順を指定しないとページ境界で行が抜ける。
抜けた行は「未処理」と誤判定されて毎回やり直しになり、実行時間に直接効く。
さらに用途によってはデータが同期されないまま残る correctness の不具合になる。
.order("id", { ascending: true }) // ← range の前に必ず
.range(from, from + 999);
3-3. 「並列度を上げる」は最後。先に実効レートを出す
並列度を上げても効かないことがある。 実測例: 外部APIを読む処理で並列度を 5 → 12(2.4倍)にしたのに -19% しか縮まなかった。 上げた分がレート制限のリトライ待ちに変わっただけだった。
見分け方 — 進捗ログの傾きを見る:
...12/796 件 ← ここまで 35件/min
...132/796 件
...252/796 件 ← ここは 13件/min ← 明らかに律速が変わっている
着手前にこれを出す:
実効レート = 総件数 ÷ 実時間(分)
これが API のレート上限に対して十分低いなら並列度に伸びしろがある。上限近くなら並列度ではなく読み取り回数を減らすしかない。
3-4. 読み取り回数を減らす(鮮度管理)
「読んだが何も変わらなかった」件数を数える。それが6割を超えるなら鮮度管理を入れる。
- 対象に
synced_atを持たせ、--stale N(N日以上経過したものだけ再取得)にする - 状態が確定したもの(完了・クローズ済みなど)は再取得しない
4. 「遅いステップ」が処理量ではなく欠陥のことがある
これは実測しないと絶対に気づかない。同じステップの出力を複数 run 並べて比較する。
for id in $(gh api "repos/$REPO/actions/workflows/<file>.yml/runs?event=schedule&per_page=6" --jq '.workflow_runs[].id'); do
gh run view $id --repo $REPO --log 2>/dev/null | grep -aoE '\{"ok":true[^}]*\}' | head -1
done
実測例 — 6連続の run がまったく同じ出力を返していた:
{"ok":true,"responses":320,"inserted":0,"updatedExisting":27,"skipped":34}
{"ok":true,"responses":320,"inserted":0,"updatedExisting":27,"skipped":34}
...
inserted:0 なのに updatedExisting:27 が毎回同じ = 同じ27件を毎回処理し直している。
原因は「処理済みマーカーを書く update のエラーを見ていない」ことだった。
ユニーク制約で弾かれているのに成功としてカウントしていたので、永久に「未処理」のまま。
inserted:0とupdatedExisting:N>0が毎回一致していたら、「冪等に動いている」ではなく「マーカーが書けていない」を疑う。
この形の欠陥は、課金だけでなく 外部APIの無駄打ち(OCR/LLM等の従量課金)とデータ欠損を同時に起こしている。
5. 🚨 やってはいけないこと
5-1. 性能実測のために本番の夜間ジョブを日中に再実行しない
「全件再取込 → dedup → archive」を1本に持つジョブは、自分の出力が次回の入力になる。 1日1回前提で設計されているので、短時間に2回走らせると 1回目の dedup が状態を変え、2回目の取込が既存レコードを照合できず大量に新規作成する。
実害の実測例(50分間隔で2回実行):
| run | 結果 |
|---|---|
| 通常の定期実行 | inserted=4 / updated=1293 |
| 1回目(手動) | inserted=8 / updated=1290 |
| 2回目(手動) | inserted=442 / updated=855 |
2回目が442件の重複レコードを作り、うち343件は過去に意図的に除外(archive)されていたレコードの復活だった。 金額付きのレコードが復活したため、業務アプリの集計が大きく狂った。
実行時間を測りたいなら:
- 過去 run の
/actions/runs/{id}/jobsからステップ時刻を採る(追加実行ゼロ) - スクリプト単体を
--dry-runでローカル実行する - どうしても実走が要るなら 次の定期実行を待つ
判断基準は「このジョブは冪等か」ではなく 「このジョブは自分の出力を次回の入力にしていないか」。
5-2. 前提を検証せずに実装しない
「マーカーが書けていない」→「だから対象カラムは NULL のはずだ」と推論して
WHERE col IS NULL 付きの更新を実装したところ、対象217件のうち16件しか当たらなかった(残りは既に別の値を保持)。
実態は「1レコードに複数の入力行が対応する」で、単一値カラムは構造的にマーカーにできなかった。
select count(*) from t where col is null を1回投げていれば実装せずに済んだ。
推論で前提を埋めない。1クエリで確かめる。
5-3. 事故後の巻き戻しは範囲を時刻で切らない
「12:30以降に作成されたレコードを全部戻す」とやると、その間に走った正常な処理の結果まで巻き込む。 実際に正常な新規9件を誤って無効化した。
- 巻き戻しは 識別マーカー(
archived_by等)を入れて可逆にする - 適用後に read-back verify(期待件数と一致するか)を必ず行う
- 時刻で切るなら、その区間に他の処理が走っていないかを先に確認する
6. 課金上限に当たると private リポの cron が全滅する
Actions の予算上限に達して Stop usage when budget limit is reached が発動すると、
private リポの全ワークフローが起動しなくなる。public リポは無料枠なので動き続けるため、
「一部だけ動いている」という紛らわしい状態になる。
見分け方(この文字列が出たらコードを疑わない): gh run view <id> の ANNOTATIONS に
The job was not started because recent account payments have failed
or your spending limit needs to be increased
ジョブが起動していないので --log-failed はログを返さない(404 / BlobNotFound)。失敗ステップも空になる。
.jobs[].stepsに failure が1つも無いなら、コードではなく課金・権限側を見る。
予算上限の変更は支払い操作なので API では不可(user/settings/billing/* は 404、読み取りにも user スコープが要る)。
人間に Billing 画面で上げてもらう。Stop usage のチェックは外させない(硬い上限を残したまま枠だけ広げる)。
Receive budget threshold alerts(75/90/100%)は常に ON にしておく。
7. 進め方のテンプレ
- 全ワークフローの月課金分を出し、上位2本に絞る
- その2本をステップ別に割り、上位2ステップに絞る
- 短時間高頻度 → 統合(実発火時刻で統合先を選ぶ /
if: !cancelled()) - 長時間 → per-row 往復の一括化 → ページングの
order→ 実効レートを見てから並列度 → 鮮度管理 - 複数 run の出力を並べ、同じ数字が続いていないか(欠陥の兆候)を見る
- 変更は1本ずつ出し、変更前の出力カウンタと機械照合して回帰が無いことを確認する
- 削減額は次の請求サイクルで実測して報告する(見積りだけで「完了」と言わない)
見積りを外したときは訂正する
実測例では「-1,350分/月」と見積もったが、実測は -530分/月 だった(並列度の効果を楽観視していた)。 PR 本文の見積りは実測が出た時点で訂正する。楽観的な見積りを残したまま閉じない。
よくある質問
+「GitHub Actions の課金分を実測ベースで削る」とは何ですか?
Actions の請求を推測でなく実測から削る手順。ジョブ単位の分切り上げという課金構造、ステップ別に時間を割る API の叩き方、短時間高頻度は統合・長時間は per-row 往復の一括化という打ち分け、並列度を上げても効かないケースの見分け方、そして『遅いステップが実は同じデータを毎回処理し直している欠陥』の見つけ方まで。本番の夜間ジョブを実測目的で再実行して重複レコードを大量生成した失敗例と、その巻き戻しの作法も含む。
+どれくらいトークン(費用)を節約できますか?
ゼロから開発すると約12万トークンかかりますが、この巻物を使えば約1.5万トークンで済みます。差し引き約10.5万トークン(API料金換算で約160円)・88%の節約です。
+どうやって使いますか?
無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。
+どのAIツールに対応していますか?
claude-code、cursor、codex-cli に対応しています。
+商用利用できますか?
ライセンスは「商用利用可 (再販不可)」です。
🤝 自分でAIを動かすのは、まだ不安…という方へ
この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。
関連する巻物
ドキュメント駆動開発プロセス CLAUDE.md — 作るものを固めてから書かせる
「AIが暴走して意図と違うものを作る」を根絶する開発プロセス指示書。UI仕様→機能設計→実装の順をAIに強制し、1ファイルごとに承認ゲートを挟む。受託開発・チーム開発向け。
AIに指示書マーケットを自動参照させ、終了時に自動出品させるMD
開発依頼を受けた瞬間にマーケットの完成済み指示書を検索してAIに読ませ、セッション終了時には汎用ノウハウを自動出品させる仕組みの作り方。全台配布・秘密情報スキャン・実際に踏んだ配布バグ3つの回避込み。
「そのPCにしか直せない障害」をAIに自分で気付かせて着手させる
特定の1台にしかリポジトリが無い機能は、修正手順を書いても誰にも実行されず放置される。SessionStart hook で当該PCのAIだけに指示を出し、完了後は指示書へ状態を書き戻して再実装事故を防ぐ型。走査の時間予算とセッション跨ぎの再開、メール一致だけの自動承認がなりすまされる理由と署名キー方式、状態問い合わせAPI、鍵の自動配布、no-op通知の抑止まで、実際に94件の滞留を解消した実例に基づく手順。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア