Fix GitHub Actions Workflow Stuck in 'Queued'
The Symptom
You push a commit or trigger a workflow, and the GitHub Actions run shows "Queued" indefinitely. The job never picks up a runner and the status spinner keeps spinning with no progress.
Queued — Waiting for a runner to pick up this job...
Root Cause
A workflow stays queued when no runner is available to execute it. The most common causes are:
- Self-hosted runner is offline. The runner machine is stopped, the runner service crashed, or the runner lost connectivity.
- Label mismatch. The job requests
runs-on: custom-labelbut no registered runner has that label. - Concurrency limits hit. Your plan's concurrent job limit is reached, or a
concurrencygroup is blocking the run. - GitHub-hosted runner capacity. Rare, but GitHub occasionally has capacity issues for specific runner types (especially macOS or large runners).
- Required approvals. Workflows from fork PRs or first-time contributors require manual approval.
Step-by-Step Fix
1. Check if approval is required
Go to the Actions tab in your repository. If you see a yellow banner saying "Approve and run," click it to approve the workflow. This is common for first-time contributors.
2. Verify self-hosted runner status
Go to Settings > Actions > Runners and check the runner status:
# On the runner machine, check the service
sudo systemctl status actions.runner.*.service
# If stopped, start it
sudo systemctl start actions.runner.*.service
# Check runner logs
cat /home/runner/actions-runner/_diag/Runner_*.log | tail -50
3. Verify runner labels match
Compare the runs-on value in your workflow with the runner's assigned labels:
# Workflow file
jobs:
build:
runs-on: [self-hosted, linux, x64, production]
The runner must have ALL listed labels. Check in the repository settings or via the API:
gh api repos/{owner}/{repo}/actions/runners --jq '.runners[] | {name, status, labels: [.labels[].name]}'
Fix a label mismatch by updating either the workflow or the runner's labels in the settings UI.
4. Check concurrency groups
If your workflow uses concurrency groups, a pending run may be waiting for a previous run to complete:
concurrency:
group: deploy-production
cancel-in-progress: false
With cancel-in-progress: false, new runs queue behind the active one. Check the Actions tab for an in-progress run in the same group. Either wait for it to finish or cancel it:
gh run list --workflow=deploy.yml --status=in_progress
gh run cancel <run-id>
5. Check your plan's concurrency limits
GitHub plans have concurrent job limits:
| Plan | Concurrent jobs |
|---|---|
| Free | 20 |
| Team | 40 |
| Enterprise | 500 |
For self-hosted runners, there is no GitHub-imposed limit, but your runner pool size is the practical limit.
Check how many jobs are currently running:
gh run list --status=in_progress --limit=50
6. Re-run the workflow
If the runner is healthy but the job is still stuck (possibly a transient GitHub issue):
gh run rerun <run-id>
Prevention Tips
- Monitor self-hosted runners. Set up health checks and alerts for the runner service. Use a process manager like systemd to auto-restart the service on failure.
- Use runner scale sets. For Kubernetes-based runners, use Actions Runner Controller (ARC) to auto-scale runners based on demand.
- Set
cancel-in-progress: truefor non-critical workflows. This prevents queue pileups on frequently triggered workflows like CI checks. - Add a timeout to jobs. Use
timeout-minutesto prevent zombie runs from blocking concurrency groups indefinitely. - Use GitHub's larger runners for predictable capacity. GitHub-managed larger runners provide dedicated capacity and avoid shared pool contention.
Was this article helpful?
SRE & Observability Engineer
If it's not measured, it doesn't exist. SLO-driven, metrics-obsessed, and the person who gets paged at 3 AM so you don't have to. Observability isn't optional.
Related Articles
Fix GitHub Actions 'Resource not accessible by integration'
Resolve the GitHub Actions 'Resource not accessible by integration' error by fixing workflow permissions, GITHUB_TOKEN scopes, and repository settings.
The Complete Guide to GitHub Actions CI/CD: From Zero to Production-Ready Pipelines
Build production-grade GitHub Actions CI/CD pipelines — from first workflow to reusable workflows, matrix builds, and deployment gates.
GitHub Actions Reusable Workflows and Composite Actions for DRY Pipelines
Eliminate duplicated CI/CD logic across repositories using GitHub Actions reusable workflows and composite actions with real-world examples.
Hardening GitHub Actions: Permissions, OIDC, and Pinned Actions
Harden GitHub Actions security with least-privilege permissions, OIDC federation, SHA-pinned actions, and secrets management best practices.
GitHub Actions Matrix Builds for Multi-Platform Testing
Master GitHub Actions matrix builds to test across multiple OS versions, language versions, and configurations in parallel.
Apache 301 Redirect Loop: Diagnosing And Fixing Infinite Redirect Chains In VirtualHost And .htaccess Configurations
Few things are more frustrating than deploying what you think is a clean redirect configuration, only to have your browser throw an `ERR_TOO_MANY_REDIRECTS...
More in CI/CD
View all →GitLab CI Dynamic Child Pipelines For Monorepo Microservices Deployment
After a decade of wrestling with CI/CD systems and watching monorepo deployments go from "simple" to "nightmare" and back to "elegant," I've seen every per...
GitLab CI Pipeline Optimization: Caching, DAG, and Parallel Jobs
Cut your GitLab CI pipeline time from 25 minutes to 6 with smart caching, DAG dependency graphs, parallel test splitting, and stage optimization.
Automated Semantic Versioning with Conventional Commits and release-please
Stop manually bumping versions. Use conventional commits and release-please to automate versioning, changelogs, and releases.