A GitHub Actions job stays queued or hangs with no output

A job that never leaves Queued is waiting on something: a concurrency group, a required environment approval, or a runner label with no capacity. A job that is running but silent is usually a hung step with no timeout-minutes, or a fixed wait for a service container. The fix depends on which one it is.

runtime · needs run history
How StarSling works

AI agents find the cause

StarSling agents run this diagnosis against your workflows and run history, identify which cause is yours, and open the fix as a reviewable PR.

Symptoms

GitHub only starts a job once it has both an eligible runner and a clear path through any concurrency group and environment gates you have configured. When a job sits at Queued or Waiting for a runner, the run history and the Actions UI tell you which of those it is stuck on, the workflow file alone will not, because none of these are visible as YAML defects: a concurrency group is working exactly as configured, a runner label with a small pool is a capacity fact about your account, and a required environment approval is a policy, not a bug. A job that has already started and shows no new log lines is a different problem: it has a runner, but a step is blocked on something that never returns, most often because there is no timeout-minutes to kill it, or because it is waiting a fixed amount of time for a database or cache container that was not actually ready yet. The five causes below are ordered by how each one presents, starting with the two queue-blocking causes you rule in or out from the run list, then the running-but-silent causes you rule in or out from the job log and its runs-on label.

  • The job has said Queued or Waiting for a runner for several minutes and the progress bar has not moved.

  • Other jobs for the same branch or pull request are running or queued at the same time you expected this one to start.

  • The job shows as In progress, but the log panel has not printed a new line in a long time.

  • The run eventually times out at exactly 6 hours with no clear failure message.

  • The job only gets stuck on a specific runner label, such as macOS, Windows, arm64, or a self-hosted label, while jobs on the default Linux runner start immediately.

How to diagnose it

  1. Open the run in the Actions UI first. If it shows a Review pending deployments banner or a padlock icon next to an environment name, the job is waiting on a required environment approval, not a queue or capacity problem, resolve it from the environment's required reviewers, not from any cause below.

  2. If there is no approval gate, pull the job list for the run: gh api "repos/{owner}/{repo}/actions/runs/{run_id}/jobs" --jq '.jobs[] | {name, status, runner_name}'. A job with "status": "queued" and a null runner_name has not attached to a runner yet, move to the queue-side causes. Do not discriminate on started_at: GitHub stamps it on queued jobs too, so it is never null and a check waiting for null will never fire.

  3. For a queued job, list other runs on the same branch with gh api "repos/{owner}/{repo}/actions/runs?branch=<branch>" - quoted, because an unquoted ? is a shell glob character - and check for an in-progress or queued run that overlaps it, that points to the concurrency-group or superseded-run causes.

  4. Check the queued job's runs-on label in the same jobs response. A non-default label (Windows, macOS, arm64, or a self-hosted label) with no online runner registered explains the wait on its own, independent of concurrency.

  5. If the job's status is "in_progress" instead, open its log and note the timestamp of the last printed line. A large gap with no new output means it is not queued at all, move to the timeout and container-healthcheck causes.

  6. For an in-progress, silent job, check whether the current step is a service container step or a step immediately after one; a fixed sleep before that step is the most common source of unexplained silence.

Likely causes

Ordered by how often each one turns out to be the answer. Confirm a cause with its check before you change anything.

  1. The run is queued behind a concurrency group

    A concurrency: block with cancel-in-progress: false (or no cancel setting) lets one workflow run at a time per group, so a new run for the same branch waits for the current one to finish rather than replacing it. If the group key is scoped too broadly, unrelated branches can end up sharing one slot and queuing behind each other.

    Confirm it

    In the Actions UI, open the run and look for a banner naming the concurrency group it is waiting on. Or list runs for the branch: gh api repos/{owner}/{repo}/actions/runs --jq '.workflow_runs[] | select(.head_branch=="<branch>") | {id,status,run_started_at}' and see if an earlier run is still in_progress or queued.

    Fix: Concurrency groups
  2. Superseded runs from rapid pushes are backed up in the queue

    Pushing several commits to the same branch in quick succession without cancel-in-progress: true leaves every earlier run occupying a runner slot instead of being cancelled. The newest run then queues behind runs whose results nobody needs anymore, which reads as the same job being stuck even though nothing is actually broken.

    Confirm it

    gh api repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs --jq '.workflow_runs[] | select(.head_branch=="<branch>") | {id,status,created_at,run_started_at}' and check whether multiple runs for the branch overlap in time instead of one finishing before the next starts.

    Fix: Superseded runs cancelled
  3. The job is running but a step is hung with no timeout to kill it

    A job without timeout-minutes defaults to a 6-hour ceiling, so a step that never returns (a process waiting on input, a network call with no client-side timeout, a deadlocked test) can occupy the runner for hours while producing no new log output. This looks identical to a queueing problem from the PR checks list, but the job has already started.

    Confirm it

    Open the job's log and compare the timestamp of the last printed line to the current time. If the job status is in_progress with a large gap and no new lines, pull the job definition and check for timeout-minutes: gh api repos/{owner}/{repo}/actions/jobs/{job_id} and look at status, started_at, and how long it has run relative to any timeout set in the workflow file.

    Fix: Job timeouts
  4. The job is waiting on a service container that never signalled ready

    A workflow that starts a Postgres, Redis, or similar service container and waits with a fixed sleep N instead of a healthcheck can stall silently if the container takes longer than N seconds to accept connections. The job keeps running, but produces no output while the sleep or the first dependent step retries against a port that is not open yet.

    Confirm it

    grep -n 'sleep [0-9]' .github/workflows/*.yml for the workflow in question, and check whether the services: block for that container defines options: --health-cmd. If there is a sleep instead of a healthcheck, that is the wait you are seeing in the silent log.

    Fix: Container healthchecks
  5. The job is queued on a runner label with a small capacity pool

    Non-default runs-on labels, Windows, macOS, arm64, or a specific self-hosted label, draw from a much smaller pool of available runners than the default Linux hosted runner. A job pinned to one of those labels can queue for a long time even with no concurrency group involved, and a self-hosted label with no online runner registered queues indefinitely.

    Confirm it

    gh api "repos/{owner}/{repo}/actions/runs/{run_id}/jobs" --jq '.jobs[] | {name, status, labels, runner_name}' to see the exact runs-on label and whether runner_name is null while status is queued - that pair is the unassigned-runner signal, not started_at, which GitHub populates on queued jobs as well. For self-hosted labels, cross-check gh api "repos/{owner}/{repo}/actions/runners" --jq '.runners[] | {name,status,labels}' for an online runner carrying that label.

    Fix: Right-sized runners

Hand it to an agent

Hand this prompt to your coding agent (Claude Code, Cursor, and the like) to run this diagnosis against your repository and report which cause it found.

Prompt for your coding agent
A GitHub Actions job in this repository is either stuck at Queued/Waiting for a runner, or shows In progress with no new log output. Diagnose which of the following it is, using the run history and Actions REST API, not just the workflow YAML. Report the cause before changing anything. First, check the run in the Actions UI for a required-environment-approval banner, if present, stop there, it is not one of the causes below. Otherwise call `GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs` for the stuck job. If `status` is `queued` with a null `runner_name` (not a null `started_at` - GitHub stamps that on queued jobs too): check for an overlapping in-progress or queued run on the same branch (points to a missing or overly narrow `concurrency:` block, or missing `cancel-in-progress: true`), and separately check the job's `runs-on` label against `GET /repos/{owner}/{repo}/actions/runners` for online capacity. If `status` is `in_progress`: read the job log's last timestamp, then check the workflow file for a missing `timeout-minutes` and for a `sleep` before a service-container-dependent step. Report which single cause the evidence supports.

Go further

Find the cause, fix them all, or keep them fixed.

You have the prompt that works out which cause applies. Here is how much further you can take it, each step doing more for you than the last.

  1. Find the cause

    Copy the diagnosis prompt above

    Hand it to your coding agent to confirm which cause applies in your repo. It reports back what it found and changes nothing.

  2. Fix them all, once

    Install the ci-speedup skill

    One prompt audits your whole repo against all 73 ci-speedup patterns (these 7 plus 66 more) and hands your agent every fix at once. Open source, MIT, runs locally.

  3. Keep it fixed, forever

    Install the StarSling GitHub App

    Connect GitHub and the fixes stay applied as your CI evolves, with agents that keep inspecting your workflows and opening optimization PRs you review.

Sources

1REST API: List jobs for a workflow run (opens in new tab)

2Control the concurrency of workflows and jobs (opens in new tab)

3Using environments for deployment (required reviewers) (opens in new tab)

4Monitoring and troubleshooting self-hosted runners (opens in new tab)

Last updated 2026-08-31