How to Measure CI Queue Time and Its Hidden Cost
Queue time is invisible on the minutes bill but expensive in engineer wait and, for self-hosted fleets, in mis-sized capacity.
Compute the gap between when a run is created and when its job starts, using the run and jobs API timestamps, then track it to right-size capacity.
Steps
- Read
created_atfor the run andstarted_atfor each job from the API. - The difference is queue time; track its distribution, not just the average.
- Rising queue time on self-hosted means scale up; on hosted it signals concurrency limits.
Compute queue time for a run
Terminal
# started_at minus created_at, per job, in seconds
curl -sS -H "Authorization: Bearer $GH_TOKEN" \
"https://api.github.com/repos/my-org/api/actions/runs/$RUN_ID/jobs" \
| jq -r '.jobs[] | "\(.name) \((.started_at|fromdate) - (.created_at|fromdate))s"'Gotchas
- On hosted runners, long queues usually mean you hit a concurrency limit, not a runner shortage.
- On self-hosted, idle runners waiting for work cost money while queues elsewhere grow; balance the pool.
- Queue time does not appear in billed minutes, so it hides until you measure it deliberately.
Related guides
How to Scale Runners to Zero to Cut Idle CostEliminate idle self-hosted runner cost by scaling the fleet to zero when no jobs are queued, using an autosca…
How to Find Your Most Expensive WorkflowsRank GitHub Actions workflows by billed minutes to find the few pipelines that drive most of your CI spend, u…