Signals
What makes your CI slow, costly or busier than it needs to be, and which workflows it affects.
The Signals page, under Analytics, points out patterns in your CI that cost you time or money. Each signal has the same title every time, with the repos and workflows it affects listed underneath, most minutes first.
Signals are grouped by the question they answer:
- Speed: why is CI slow?
- Cost: why does CI cost this much?
- Complexity: is CI doing more than it needs to?
Below the signals, the Breakdown shows who and what uses your CI minutes.
Every area shows on the page, even when none of its signals came up. Signals that didn’t come up are listed under their area, after No sign of, so you can see everything the page checks for. The ⓘ beside any signal explains it and links to its section here.
Click a row under any signal to see the runs behind it, newest first. Each run opens in History with its jobs, and on signals about one job, that job.
The page updates every hour. A signal shows up once it covers at least 30 minutes and 1% of all your job minutes in the range, so small one-offs stay off the page. Minutes and spend sit in separate columns. Spend uses the same list prices as the Usage page, so public repos and self-hosted runners count as free.
Speed signals
Section titled “Speed signals”Waiting for a runner and waiting on one job cost time rather than money, so those two signals leave the spend column empty.
Waiting for a runner
Section titled “Waiting for a runner”These jobs waited more than a minute after GitHub queued them before a runner took them, grouped by their runs-on labels. Jobs wait when every runner with those labels is busy, or when the account reaches GitHub’s limit on jobs running at once. Add a step to your runner order so jobs go to the next runner that’s free instead of waiting.
Click a runner’s name to open its panel, with its jobs per day and recent jobs. When every waiting job ran on the same machine, the name opens that machine.
One job sets the pace
Section titled “One job sets the pace”This job ran alongside the others in its run and was still going after they all finished, so the run waited on it alone. The minutes are that wait. Jobs that wait for another job with needs aren’t counted, since they start after it by design. Split the job, or compare its times on each runner on the Performance page.
Flaky jobs
Section titled “Flaky jobs”These jobs failed in one of their steps and then passed when the run was tried again, so the failure likely had nothing to do with the change. The minutes and spend are the failed attempts. Fixing the test or step that fails now and then saves the re-run and the wait for it.
Runner failures
Section titled “Runner failures”These jobs failed before any of their steps started, grouped by their runs-on labels. The runner was the problem, not your code. Turn on automatic retries to send them to another runner.
Cost signals
Section titled “Cost signals”No concurrency limit
Section titled “No concurrency limit”These runs kept going after someone pushed again to the same branch. The minutes count from when the newer run started. Your default branch is left out, since most teams want every commit there to finish.
Add a concurrency group with cancel-in-progress to the workflow, and GitHub cancels the older run when a newer one starts:
concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: trueSame commit runs twice
Section titled “Same commit runs twice”The workflow ran for the same commit from both push and pull_request. The push runs are the extra ones, so their minutes and spend are what the page shows. Limit push to your default branch and the pull request run still covers every change.
Short jobs rounded up
Section titled “Short jobs rounded up”GitHub bills each job by the whole minute. These jobs finished in under a minute on a billed runner, so part of every minute went unused. The page shows that unused part. Lint and other quick checks can run as optimized checks instead.
Complexity signals
Section titled “Complexity signals”Docs changes run CI
Section titled “Docs changes run CI”The workflow ran for a pull request that only changed docs: Markdown and other prose files, anything in a docs folder, or a license. For runs without a pull request, it goes by the title instead: commits titled as docs changes, such as docs: fix a typo in Conventional Commits. Workflows with “doc” in their name are left out, since building docs is their job. Add paths-ignore to the workflow’s push and pull_request triggers so changes that only touch docs skip it:
on: pull_request: paths-ignore: - "docs/**" - "**/*.md"Full matrix on every branch
Section titled “Full matrix on every branch”The workflow started 10 or more jobs for a push or pull request on a branch other than your default, usually a wide matrix. All of those runs’ minutes count. Running a smaller matrix before merge, and the full one on your default branch, still catches most problems early.
Breakdown
Section titled “Breakdown”The Breakdown splits all your job minutes and spend one way at a time:
- Person: who started the run.
- Repository.
- Workflow, with its repo.
- Branch.
- Trigger: push, pull request, schedule, manual or re-run.
- Changed folder: the folders a pull request changed, with their repo, such as
srcordocs. Inside folders likeappsandpackages, each project is its own folder, such asapps/web. Only pull request runs count here, and a run counts under every folder it changed, so the rows overlap.
Each view lists the 10 with the most minutes, then everything else in one row. Runs count once each, so a run with a 20-job matrix is still one run.
To know which folders a pull request changed, the dashboard reads the names of its files from GitHub once an hour. It keeps only the folders, never the files’ contents.
Filter the page
Section titled “Filter the page”Click a row in the Breakdown to narrow the whole page to it: the signals, their rows and the other Breakdown views then only count that person’s, repo’s, workflow’s, branch’s, trigger’s or folder’s runs. Click a row in another view to narrow it further, and use the × beside a filter to remove it.
A filtered page also shows that filter’s minutes per day, with the same number of days before as a dashed line, so you can see whether it’s growing. Filtered pages cover up to the last 30 days.