GitHub Actions Job Stuck "Waiting for a runner to pick up this job"
A job is queued but no runner ever picks it up. Either no online runner matches every requested label, your self-hosted runner is offline, or you have hit a concurrency or billing limit.
What this error means
The job sits indefinitely with "Waiting for a runner to pick up this job" and never starts. There is no failure, just an unbounded queue wait.
Requested labels: self-hosted, gpu, linux
Waiting for a runner to pick up this job...Diagnose it: is the job queued, or is the runner gone?
A job that never starts and a job whose runner disappeared mid-run look similar in the UI and have opposite causes. The first is a labelling or capacity problem, the second is the runner being killed, usually by memory pressure or a spot reclaim.
- name: Runner facts
run: |
echo "runner name: $RUNNER_NAME"
echo "os/arch: $RUNNER_OS/$RUNNER_ARCH"
nproc; free -h; df -h /
echo "labels this job asked for: ${{ toJSON(job) }}"Common causes
No runner matches all requested labels
runs-on requires a runner that carries every label listed. If no online runner has all of them (for example a missing gpu label), the job waits forever.
Self-hosted runner offline or busy
A self-hosted runner that crashed, lost network, or is already running its max jobs cannot accept the queued job.
Out of concurrency or minutes
Hitting the plan concurrency cap, or running out of included minutes/billing, leaves hosted jobs queued until capacity frees up.
How to fix it
Match runs-on to an available runner
Use a label set that an online runner actually carries, or a valid GitHub-hosted label.
# GitHub-hosted
runs-on: ubuntu-latest
# self-hosted: every label must exist on an online runner
runs-on: [self-hosted, linux, x64]Bring runners online and check limits
- Confirm self-hosted runners show as Idle/Online in repo or org settings.
- Check that billing is active and you have not exhausted included minutes.
- Verify you are within the concurrent-jobs limit for your plan.
The failures that are not your workflow
- Exit 137 is the kernel out-of-memory killer, not an application error. Check
free -habove against your peak usage. - Disk exhaustion presents as unrelated write errors deep in a build. GitHub-hosted runners ship roughly 14 GB of free space, which a Docker-heavy job can exhaust.
- A lost connection to the server on a self-hosted runner is usually the host being reclaimed or rebooted, not a network fault in your job.
- A job that starts and immediately fails with no step output normally failed during runner setup, before your workflow ran at all.
How to prevent it
- Keep runs-on labels in sync with the labels your runners actually register.
- Monitor self-hosted runner health and auto-restart offline runners.
- Watch concurrency and minutes usage so jobs do not silently queue.