Performance
Queue Time
Queue time is the wait between a GitHub Actions job entering the queue and a runner starting it, measured apart from how long the job then takes to run.
Queue time is the interval between the moment a GitHub Actions job enters the queue and the moment a runner claims it and starts the first step. It measures the wait for a machine, and it is a separate interval from run time, which covers how long the steps take once a machine is working on them.
Both intervals land on the same pull request check as wall clock, which is why they get blamed for each other. A team that shortens its test suite and sees no change in the time to a green check was usually waiting for a machine rather than running slow steps.
Definition
The workflow jobs REST API provides started_at and completed_at. It does not provide a general job created_at field from which queue time can be computed. A workflow run's creation time is not a substitute, because jobs may become eligible at different times.
| Interval | Measurement | Meaning |
|---|---|---|
| Queue time | Recorded queue-entry time to execution start | Waiting for an eligible execution environment |
| Run time | completed_at minus started_at | Time spent executing the job |
| End-to-end latency | Explicitly chosen event to completion | Can include dependencies and approval delays |
Queue time is a property of one job rather than one run. A run made of twelve matrix legs has twelve queue times, and the run finishes when the slowest chain of queue plus run finishes. Reporting a single number per run therefore averages away the leg that waited.
Four things live inside a queue time, and they have different fixes:
- Every eligible machine is busy. The job waits for one to free up. This is a depth problem in the set of runners that carry the label.
- No machine exists yet. An autoscaled fleet creates one on demand, so the wait includes boot, agent registration, and image pull before the first step runs.
- A ceiling is holding the job back. Account concurrency limits, a
concurrencygroup in the workflow, or an unfinishedneedsdependency all keep a job out of a runner while nothing is wrong with capacity. - No machine will ever match. A misspelled label, a repository outside a runner group, or a workflow restriction leaves the job queued with an empty log. GitHub cancels a job that has waited 24 hours for a self-hosted runner (GitHub Actions limits, checked on 2026-08-13), so this failure mode surfaces a day after the push.
Cause three is worth separating in any measurement. A job blocked by needs or by a concurrency group is reported as waiting, so subtracting timestamps across every job in a run mixes dependency waiting into machine waiting. The workflow_job webhook avoids the ambiguity: it delivers queued when the job enters the queue, in_progress when a runner starts it, and completed when it ends, and each payload carries the job's labels array and runner_name (workflow_job webhook payload, checked on 2026-08-13). Recording those deliveries can estimate both intervals with the routing key attached. Deduplicate deliveries, account for retries and delivery delay, and do not treat webhook receipt time as an exact server timestamp.
Read the result as percentiles. Queue time has a long tail by nature, since most jobs on a quiet afternoon start immediately and the jobs that queue arrive together during a busy hour. A mean across a week reports a healthy number while the P90 sits in minutes.
Example
For an existing WarpBuild deployment, use the Jobs and queue timings reports to compare queue and execution duration. If collecting your own data, store queued and started observations per job and run attempt before computing percentiles.
The following conceptual calculation assumes queued_observed_at and started_observed_at are timestamps your own event collector recorded; these are not GitHub REST response fields:
estimated_queue_seconds = started_observed_at - queued_observed_at
run_seconds = completed_at - started_atAn illustrative report has one row per label. The numbers below illustrate the shape of a week from one repository rather than a measurement:
| Runner label | Jobs | Wait P90 | Run P90 |
|---|---|---|---|
ubuntu-latest | 355 | 8s | 5m 02s |
self-hosted,linux,x64 | 412 | 6m 40s | 4m 10s |
self-hosted,linux,arm64 | 96 | 1m 12s | 9m 30s |
Three labels, three different problems. The hosted label starts almost immediately and spends its time in the steps. The x64 self-hosted label waits longer than it runs, which points at how many machines carry that label and how quickly new ones appear. The ARM64 label starts quickly and runs for nine minutes, so adding machines there changes nothing and the steps are the place to look.
A single average across all three labels would report roughly four minutes of wait and hide the row that matters. Grouping by label keeps the routing key attached to the number, which is also the key a fix is applied to, since capacity, scope, and machine size are all configured per label.
Related Terms
- GitHub Actions queue times and how to fix them: the five causes behind a long wait and the fix that matches each one.
- Runner pool, the set of machines behind a label: how depth, warm floor, and idle timeout decide whether a job waits.
- Cold start, the machine creation time inside a wait: what a job pays for when no machine exists yet.
- Queue timings and job reports: P75 and P90 wait per runner label and stack, with CSV export.
- Runners not picking up jobs: the bot access, runner group, and workflow restriction checks for a job that never starts.
- WarpBuild pricing: per minute rates by runner type.
FAQ
What is queue time in GitHub Actions?
Queue time is the interval between a job entering the queue and a runner claiming it and starting the first step. It measures the wait for a machine. Run time, the interval from the first step to the job conclusion, measures the work itself. A job that shows Waiting for a runner to pick up this job is accumulating queue time.
How do I measure queue time for a job?
Use a documented queue-time report or record workflow_job queued and in_progress events. The workflow-jobs REST response provides started_at and completed_at but should not be assumed to provide a job-created timestamp. Webhook receipt timing is approximate and requires deduplication.
Why report queue time per runner label instead of one average?
The two intervals have different fixes and one average hides which one is growing. A label whose wait exceeds its run time points at capacity or routing. A label whose run time dominates points at the steps. Reporting P75 and P90 per label keeps the two apart, because a long tail on a small share of jobs disappears in a mean.
Sources
- docs.github.com · workflow jobs
- docs.github.com · limits
- docs.github.com · webhook events and payloads
- www.warpbuild.com · reports
Definition reviewed .
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.