GitHub Actions の cron が、ある日から 1 日 48 回中 6 回しか動かなくなった
GitHub Actions の schedule が、エラーも出さずに動きませんでした。3 日のあいだに 2 種類の止まり方を踏んで、最後に実行ログを数えたら、1 日 48 回動くはずの定期実行が 6 回しか動いていませんでした。しかも数週間前までは 1 日 25〜36 回動いていて、ある日を境に落ちたようでした。
2026 年 9 月上旬に GitHub.com で観測した記録です(GitHub CLI 2.93.0 / Hugo 0.161.1 extended)。
気づいたきっかけは、このブログの公開の仕組みでした。記事を先に書いて date を未来の日付にしておくと、Hugo は既定で未来日の記事をビルドから除外します(buildFuture)。そのままにしておけば当日まで出ないので、毎朝ビルドし直す仕組みさえあれば勝手に 1 日 1 本ずつ公開されていきます。その「毎朝」を schedule に任せていました。
症状:エラーが出ない。ただ何も起きない
厄介なのはここです。
ワークフローが発火して失敗したなら、実行履歴とログが残ります。通知を有効にしていれば通知も届きます。ところが今回の止まり方は、実行そのものが発生しません。失敗が 0 件なので、GitHub の画面上は何も問題がないように見えます。
気づいたのは「今日公開されるはずの記事が 404 だった」からでした。結果を見に行くまで分からない状態だったということになります。
止まり方その 1:60 日アクティビティが無いと自動で無効化される
最初に踏んだのはこれです。submodule のポインタを追従させるワークフローが、いつのまにか止まっていました。
手動で動かそうとして初めて気づきます。
$ gh workflow run bump-submodule-pointers.yml
HTTP 422: Cannot trigger a 'workflow_dispatch' on a disabled workflow
状態を見ると無効化されていました。
$ gh workflow list --all
Bump submodule pointers disabled_inactivity 294328658
Sync agent rules to children active 270242612
disabled_inactivity。公式にこう書かれています。
In a public repository, scheduled workflows are automatically disabled when no repository activity has occurred in 60 days.
参考: GitHub Docs - Events that trigger workflows
パブリックリポジトリ限定の挙動で、プライベートでは無効化されません。条件は「コミットが無いこと」ではなく「リポジトリのアクティビティが無いこと」で、何が数えられるかまでは明記されていません。実際このリポジトリは 3 か月近くコミットが無い時期がありましたが、その間も動いていました。
復旧は gh workflow enable ひとつで済みます。問題は、止まったことに気づく手立てが無かったことです。少なくとも私の手元には通知は届いておらず、結果として 1 週間気づきませんでした。
動いている間だけ延命される構造だった
このワークフローは、サブモジュールのポインタが動いたときだけ親リポジトリへコミットを作ります。裏を返すと、子リポジトリが静かな時期にはコミットを作れません。
そして一度無効化されると、明示的に有効化しない限り自動では戻りません。コミットを作る主体そのものが止まっているからです。気づかなければ、そのまま止まったままでした。
止まり方その 2:発火しない(公式には「遅延・ドロップしうる」とある)
enable で復旧してしばらくして、今度は別の止まり方をします。ブログの日次ビルドが、予定時刻を 3 時間半すぎても動きません。記事は 404 のままでした。
これも公式に書かれていました。
The schedule event can be delayed during periods of high loads of GitHub Actions workflow runs. High load times include the start of every hour. If the load is sufficiently high enough, some queued jobs may be dropped.
To decrease the chance of delay, schedule your workflow to run at a different time of the hour.
「毎時の始まりは混むから、時分をずらせ」と明記されています。そして私が設定していたのは 5 0 * * *、つまり毎日 00:05 UTC(JST 09:05)。ちょうど hour の始まりの直後で、避けろと書かれている時間帯に当たっていました。
素直に従って、分をずらしました。
# before
- cron: '5 0 * * *' # JST 09:05
# after
- cron: '23 0 * * *' # JST 09:23
30 分ごとに動かしているほう(さきほど 60 日で止まっていた、サブモジュール追従のワークフローです)も、*/30 は毎時 0 分と 30 分ちょうどに当たるので、分を散らしました。
# before
- cron: '*/30 * * * *'
# after
- cron: '14,42 * * * *'
これで直ったと思っていました。
翌日も動かなかった
次の日、ブログの日次ビルドはまた発火しません。記事はまた 404 です。
schedule の実行履歴を見ると、ワークフローを追加してから一度も schedule で動いていませんでした。ワークフローは active、Actions も有効、cron はデフォルトブランチにマージ済み。それでもゼロです。
さすがに 2 日続くと偶然とは思いにくいので、30 分ごとのほうの実行ログを数えてみました。こちらは頻度が高いので、統計として見られます。
実測:3 週間ぶん数えてみた
数え方はこれだけです。gh run list は --limit を付けないと 20 件で切られるので、そこだけ注意です。
$ gh run list --workflow=bump-submodule-pointers.yml --event=schedule \
--limit 400 --json createdAt --jq '.[].createdAt' \
| cut -c1-10 | uniq -c
見ているのは createdAt(実行が作られた時刻)で、UTC の日付で集計しています。ランナーの待ち時間ではなく、そもそも発火したかどうかを数えたいためです。
14,42 * * * *(変更前は */30)なので、どちらも 1 日 48 回動く想定です。
| 日付(2026年) | schedule の実行回数 | 想定 48 回に対して |
|---|---|---|
| 8/15〜8/25 | 25〜36 回 | 52〜75% |
| 8/26 | 17 回 | 35% |
| 8/27〜9/1 | 2〜6 回 | 4〜13% |
| 9/2〜9/7 | 0 回 | (60 日停止の期間) |
| 9/9 | 7 回 | 14.6% |
| 9/10 | 6 回 | 12.5% |
8 月中旬は半分以上動いていました。それが 8/26 を境に 1 割前後まで落ちて、そのまま戻っていません。
「ずっと動かなかった」のではなく「ある日から急に動かなくなった」わけです。9/8 は 60 日停止から復旧した日で稼働が半日ぶんなので、表からは外しています。
実際に走った時刻も、指定した分とはまるで違いました(以下 UTC、cron と揃えています)。
00:32 22:35 19:45 17:12 13:36 08:56 03:37
14 分でも 42 分でもありません。間隔も 2〜5 時間ばらばらです。cron を変える前後を通して、予定した分ちょうどに走ったのは直近 90 回のうち 3〜4 回でした。
8/26 に何があったか
急落の起点が気になったので、GitHub の稼働状況を調べました。すると同じ日にインシデントが記録されていました。
On August 26, 2026 from 15:02 to 15:45 UTC, Actions jobs failed to start. The following 2 hours until 17:40 UTC, Actions runs were delayed starting by more than 5 minutes…
参考: GitHub Status - Incident with Actions
データベースのプライマリに問題が起きてフェイルオーバーし、流入を絞って回復させた、という内容でした。時期はぴったり一致します。
ただし、このインシデントは同じ日のうちに解決済みです。それなのに翌日以降も回数が戻っていないので、これが原因だと言い切ることはできません。リポジトリ側の活動量かとも思いましたが、9/8 にコミットが戻ったあとも 6〜7 回のままなので、そこでもなさそうです。
結局、理由は分かっていません。 分かっているのは「ある時点から実行率が 1 割前後に落ちて、戻っていない」という事実だけです。
なお、分をずらした効果についてもまだ判断できません。変更してからの schedule 実行は数回しかなく、統計として見るには足りないからです。
対処:schedule は「動いたらラッキー」くらいに思っておく
ここまでの挙動は、GitHub の障害というより仕様の範囲内だと思っています。ドキュメントにも最初から「遅延する」「drop されることがある」と書いてあります。私がその注意書きを軽く見ていただけでした。
とはいえ、ここまで実行されないとは思っていませんでした。「だいたい動く」ではなく「かなりの確率で動かない」を前提に設計し直す必要がありました。
現実的な対処は用途で分かれます。
動かなくても困らない用途なら、そのままでよい。 キャッシュの掃除や、次回動いたときに取り返せる集計などは、多少飛んでも実害がありません。
動かないと困る用途なら、schedule だけに依存しない。 ただし今回のような未来日公開では、push 駆動に替えるだけでは解決しません。公開時刻が来ても誰も push しないからです。外部のスケジューラから workflow_dispatch を叩く形にして、GitHub の schedule に起因する未発火から切り離すのが筋だと思っています。
正直に書いておくと、私はまだそこまで直していません。やったのは時分をずらしたことと、次に書く監視を足したことだけです。
結論:実装を変えるより、気づく仕組みが先だった
直し方を考えるより先にやるべきことがありました。止まったことに気づく仕組みです。
止まる理由は今回だけで 2 つありましたが、結果はどちらも同じ「記事が公開されない」でした。ビルドが失敗した場合も同じです。原因ごとに監視を作ると際限がないので、結果だけを見ることにしました。
- ワークフローの状態ではなく、公開されているはずの記事が実際に見えているかを確認する
- サイトの
sitemap.xmlと、リポジトリにある記事ファイルの日付を突き合わせるだけ - 欠けていたら Slack に流す。正常なら何も言わない
これなら原因が 60 日停止でも drop でもビルド失敗でも、同じように拾えます。公開情報しか見ないので認証も要りません。ただし GitHub API の未認証リクエストは IP あたり 60 回/時で、Actions の runner は IP が共有です。そのまま回すとレート制限に当たって「記事は出ているのにアラートが鳴る」ことがあるので、GITHUB_TOKEN を付けておくほうが安全です。
参考: GitHub Docs - Rate limits for the REST API
置き場所にも注意が要ります。監視自体を同じ仕組みに乗せると、一緒に死にます。 60 日ルールは public リポジトリの挙動なので、監視は private リポジトリに置きました。
ただし白状すると、この監視自体も GitHub Actions の schedule で動いています。つまり 60 日ルールは避けられていますが、遅延のほうは避けられていません。
そして、これは想像ではありませんでした。監視を入れた翌日から 2 日続けて、予定より 5 時間近く遅れて実行されていました。JST 10:37 に見るつもりが、実際に動いたのは 15 時台です。同じ朝、ブログの日次ビルドのほうは 3 日続けて発火していません。つまり「記事が公開されない」と「それに気づくのが 5 時間後」が同時に起きたわけで、書いた端から穴を踏み抜いた格好でした。
ちなみに私は最初、これを「監視も発火しなかった」と勘違いしていました。10 時半すぎに実行履歴を見て何も無かったので、飛んだものと思い込んだのです。あとで見返したら、ずっと後になって動いていました。「まだ動いていない」と「もう動かない」を、実行履歴から区別する方法はありません。
いまは「飛んだ日は翌日拾える」「気づいたら手で叩く」で妥協していますが、本来は外部の cron から叩くか、一定時間 ping が来なかったら鳴る仕組み(dead man’s switch)に寄せるべきだと思っています。監視をプライベートリポジトリに置いたのは 60 日ルールを避けるためで、障害ドメインを分けたことにはなっていません。
まとめ
- GitHub Actions の
scheduleは、エラーを出さずにただ動かないことがある。失敗通知は飛ばない - public リポジトリでは 60 日コミットが無いと自動で無効化される。
gh workflow list --allでdisabled_inactivityとして見える - 手元の実測では、8 月中旬は 1 日 25〜36 回動いていたのが、8/26 を境に 6 回前後まで落ちて戻っていない。同じ日に Actions のインシデントがあったが、解決後も戻っていないので因果は不明
- 「毎時 0 分を避ける」は公式の推奨だが、それで直ったとはまだ言えない
- 動かないと困る定期実行は、schedule に依存しない(push 駆動 or 外部スケジューラ)
- 何より先に、動かなかったことに気づく仕組みを用意する。ただし監視を同じ仕組みに乗せない(乗せた結果、監視のほうも 5 時間遅れて動いていました)
定期実行は「動いた通知」より「動かなかったことに気づく仕組み」のほうが大事だな、と思い知りました。