To eliminate GitHub Actions queue times caused by runner capacity, switch your jobs to GitHub Actions runners managed by Depot.
Depot has no concurrency limits. When GitHub sends Depot a job, Depot assigns a fresh runner from a pre-provisioned standby pool. Each job runs on its own single-tenant virtual machine, and that machine is destroyed after the job finishes.
That removes the capacity queue. A burst of 50 ready jobs can fan out across 50 runners instead of waiting for a small pool of available machines.
It doesn't remove every kind of wait inside GitHub Actions. A job can also be blocked by another job, a workflow concurrency rule, an environment approval, an incorrect runner label, or GitHub's control plane. Those waits need a different fix.
Move jobs to Depot GitHub Actions runners
After connecting a GitHub organization to Depot, change the runs-on label in the workflow:
jobs:
test:
- runs-on: ubuntu-24.04
+ runs-on: depot-ubuntu-24.04The rest of the workflow stays the same. Existing actions, secrets, permissions, matrices, and pull request checks continue to work through GitHub Actions.
The runners are ephemeral, but provisioning isn't instantaneous. Typical startup time is 10 to 45 seconds. Depot usually allocates the underlying compute in about 15 seconds, while runner registration and job dispatch depend on GitHub's control plane. Depot doesn't bill for provisioning time.
You can inspect that timing breakdown in the graph at the top of the GitHub Actions page in the Depot dashboard. If allocation regularly takes longer than one minute during normal operation, contact Depot support.
Identify the wait before changing the workflow
GitHub can show a job as queued or waiting for several different reasons:
| Cause | What is happening | Fix |
|---|---|---|
| Runner capacity | The job is ready, but no matching runner is available | Move the job to Depot |
| Job dependency | A job named in needs hasn't finished | Remove unnecessary dependencies or split the dependency graph |
| Workflow concurrency | Another run holds the same concurrency group | Narrow, remove, or intentionally cancel the conflicting group |
| Environment protection | A deployment is waiting for approval, a wait timer, or another protection rule | Review the repository environment rules |
| Runner label or group access | GitHub can't find an eligible runner for the requested labels | Correct the label and allow the repository to use the default runner group |
| Organization usage cap | A self-imposed Depot usage limit prevents another job from starting | Raise or remove the cap in Depot organization settings |
| GitHub control-plane latency | GitHub hasn't delivered, registered, or dispatched the job | Check GitHub status and compare webhook, allocation, registration, and dispatch timing |
Changing runner providers only addresses the first row. That's often the largest source of queue time during a burst, but treating every wait as a capacity problem leads to the wrong fix.
Remove accidental concurrency locks
GitHub's concurrency
setting deliberately limits how many runs or jobs sharing a key may proceed. This is useful for deployments and other
work that must not overlap. It can also serialize an entire repository by accident.
For example:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: trueThis allows only one active run for the same workflow and ref. A new run cancels the one already in progress. If the work can safely overlap, remove the concurrency block or make the group more specific. Keep it when serialization is protecting a real shared resource.
The same rule applies to deployment environments. Required reviewers and wait timers are gates, not runner queues. Faster runner provisioning shouldn't bypass them.
Parallelize jobs that are actually independent
Unlimited runner concurrency matters only if the workflow exposes parallel work. A single job with 20 sequential steps still uses one runner.
Split independent test suites into jobs or matrix shards so GitHub can dispatch them at the same time:
jobs:
test:
strategy:
matrix:
shard: [1, 2, 3, 4]
runs-on: depot-ubuntu-24.04
steps:
- uses: actions/checkout@v4
- run: pnpm install --frozen-lockfile
- run: pnpm test --shard=${{ matrix.shard }}/4Depot can provision a separate runner for every shard without a concurrency cap. This usually shortens wall-clock time, but it can increase total compute usage. Parallelize work that shortens the critical path, not every step that can be turned into another job.
Test queue behavior at peak load
An empty runner pool at 2 AM doesn't tell you what developers see after ten pull requests land together.
Measure queue and startup time during the busiest part of the engineering day. Use the same workflow, commit, runner label, and matrix width for every provider. Record p50 and p95 from the moment each job becomes ready until its first user step starts.
Then dispatch a controlled burst that resembles production traffic. If the normal peak is 40 ready jobs, test 40 ready jobs. Watch for:
- Jobs that wait because the provider has a concurrency quota or no matching capacity
- Provisioning that gets slower as the burst grows
- GitHub registration and dispatch delay after the runner is ready
- Accidental serialization from
needs,concurrency, or environment gates - Failed provisioning and automatic retries hidden inside one final status
Depot has no runner concurrency limit, so Linux and Windows jobs can scale with the exposed parallelism in the workflow. macOS uses a fixed-capacity pool because of Apple's licensing constraints and should be measured separately.
Keep the raw runs. Queue behavior is a distribution, not one screenshot. The result that matters is the p95 wait under the load your team actually creates.
Fix jobs that stay queued indefinitely
If a Depot job never starts, capacity may not be the problem. Check these configuration details:
- Use one Depot GitHub Actions runner label, such as
depot-ubuntu-24.04-4. - Don't combine it with
self-hostedin a label array. - Confirm the Depot Managed Runners GitHub app can access the repository.
- Confirm the repository is allowed to use the organization's default runner group.
- Check whether the organization has reached a self-imposed usage cap.
- Check Depot status and GitHub status for an incident.
Depot GitHub Actions runners require a repository owned by a GitHub organization. They aren't available for repositories owned by an individual GitHub account.
Linux and Windows runner capacity is elastic. macOS capacity isn't fully elastic because of Apple's licensing constraints, so macOS jobs can still see longer queue times during periods of high demand.
For current labels, machine sizes, and platform constraints, see Depot GitHub Actions runner types.