Skip to content

Skip empty schedule polls without starting a runner - #114

Open
austenstone wants to merge 3 commits into
mainfrom
austenstone-optimize-schedule-actions
Open

austenstone wants to merge 3 commits into
mainfrom
austenstone-optimize-schedule-actions

Conversation

@austenstone

Copy link
Copy Markdown
Owner

The hourly 📅 Schedule Workflow Dispatch cron boots a runner every time, even when nothing is scheduled. This gates the job on the vars context so empty polls are skipped before a runner is assigned.

if: github.event_name != 'schedule' || contains(toJSON(vars), '"_SCHEDULE_')

Schedules are stored as _SCHEDULE_* repo variables, so if none exist, the poll has nothing to do. workflow_dispatch always runs.

Baseline (last 90 days, 2026-07-13 → 2026-10-11)

Value How measured
Poller runs 1,206 schedule + 20 workflow_dispatch actions/workflows/{id}/runs?event=… total_count
Share of all repo runs ~74% 1,226 / ~1,632
Polls that had work ≤ 11 Every basic.yml dispatch in the window (11, all on 2026-08-20 test day); codeql.yml had 0
Poll job execution p50 7s, p95 9s, max 42s jobs API started_at→completed_at, last 100 cron polls (Sampled)
Job waiting p50 2s, p95 3s jobs API created_at→started_at
Rounded minutes / poll 1 (100/100) ceil(exec/60)
Billed cost $0 Public repo, standard runner

So about 1,195 runner jobs and rounded minutes per 90 days were spent listing zero variables.

After

Before After (Estimated)
Runner jobs per 90d 1,206 ≤ 11 (only polls with a pending schedule)
Rounded runner minutes per 90d 1,206 ≤ 11
Empty-poll cost 1 min, ~7s compute skipped, no runner

Verification: schedule only fires on the default branch, so I verified the gate with a temporary push-triggered probe on this branch, then reverted it. Run 38191176460:

  • contains(toJSON(vars), '"_SCHEDULE_') with no schedule vars → skipped, runner_name=null, 0s
  • contains(toJSON(vars), '"APP_ID"') (an existing var) → ran, which shows the job-level if can see vars keys

vars availability in jobs.<job_id>.if is documented in context availability.

Scope and risk

  • Not a PR check, and no required check contexts change.
  • actionlint is clean before and after. zizmor findings are unchanged (5 pre-existing: unpinned refs, app-token scope, artipacked, self-repository).
  • README example updated, so adopters' hourly polls also stop booting runners. It also notes when to drop the if: (owner/repo pointing at another repo, where vars is the wrong repo's).
  • Trade-off: a broken app key or token isn't surfaced hourly anymore. It surfaces on the next real schedule instead.

Canary/rollback: After merge, confirm cron runs show the job as skipped and that a scheduled basic.yml still dispatches at its time. To roll back, delete the if: line.

austenstone and others added 3 commits October 11, 2026 18:15
Hourly cron polls ran a 7s job (1 rounded minute) even when no _SCHEDULE_*
variable existed. Gate the job on the vars context so empty polls are skipped.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Probe run 38191176460 confirmed the gate: no _SCHEDULE_ var -> job skipped
(runner=null), existing APP_ID var -> job ran.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@austenstone
austenstone marked this pull request as ready for review October 12, 2026 02:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant