Diagnose a Failed GitHub Actions Run with gh run view
A red CI badge does not tell you which step failed: gh run view does, straight from the terminal. The examples use GitHub CLI 2.87.3, installed here as package gh. Allow about fifteen minutes. You need gh, a repository you can access, and an authenticated GitHub CLI session.
The route
Jump straight to the step you need, or tick off Done means at the end.
- 1. Check the command version and repository access
- 2. Find the run without guessing its ID
- 3. Read the summary and expose job steps
- 4. Inspect one job or one attempt
- 5. Retrieve the full or failed-step log
- 6. Produce stable machine-readable output
- 7. Make a shell check fail for a failed run
- 8. Open the run in a browser when the terminal is not enough
gh run view only reads run information, but logs can contain credentials a workflow printed by accident. Treat terminal output and saved logs as sensitive. None of the commands below need sudo or change a repository, workflow or run.
1. Check the command version and repository access
Confirm the binary and its version before trusting a flag, especially on a team where machines run different GitHub CLI releases:
$ gh --version
gh version 2.87.3 (2026-02-23)
$ gh auth status
$ gh repo view OWNER/REPOSITORY
The version line will differ on your machine. If authentication is missing, run gh auth login and complete the interactive flow: never paste a token into a shell command or a workflow log.
Checkpoint
These commands should identify an authenticated account and the repository you intend to inspect. Select a different repository explicitly when you are not sitting in its working tree.
$ REPO='OWNER/REPOSITORY'
$ gh run list --repo "$REPO" --limit 5
2. Find the run without guessing its ID
With no run ID, gh run view opens an interactive run selector, handy at a terminal but useless in a script. Get a stable ID from gh run list or a known URL first:
$ gh run list --repo "$REPO" --limit 10
STATUS TITLE WORKFLOW BRANCH EVENT ID ELAPSED AGE
✓ Build CI main push 1234567 2m about 1 hour ago
$ RUN_ID='1234567'
$ gh run view "$RUN_ID" --repo "$REPO"
The columns and values above are examples, not fixed output. Use the numeric run ID, never the pull request number or the job number. Leave off --repo and gh resolves the repository from the current directory, which is a classic way to check the wrong project after a cd.
For a quick human check, this also works:
$ gh run view --repo "$REPO"
It prompts you to choose a run. Picking the wrong item will not modify anything, but it can send the wrong ID into a later command, so note the selected run before you start investigating a failure.
3. Read the summary and expose job steps
The default view gives a run summary. Add --verbose when you need the steps inside each job, not just the headline result:
$ gh run view "$RUN_ID" --repo "$REPO" --verbose
Look for the run status and conclusion, then match a failed or skipped job to its job ID, which is not necessarily the number shown in a browser URL or a workflow display label. Ask for the structured jobs field to get the real one:
$ gh run view "$RUN_ID" --repo "$REPO" \
--json jobs \
--jq '.jobs[] | {name, databaseId, status, conclusion}'
{"name":"test","databaseId":7654321,"status":"completed","conclusion":"failure"}
Those field names are supported by the installed command, and databaseId is the value --job wants. Keep the output as a diagnostic record, but redact repository details before sending it outside the team.
4. Inspect one job or one attempt
Pass the job database ID to narrow the view:
$ JOB_ID='7654321'
$ gh run view --repo "$REPO" --job "$JOB_ID" --verbose
Workflow re-runs have attempts. The default attempt value is 0, which lets GitHub CLI pick the normal run view. To inspect a particular attempt, pass its positive attempt number:
$ ATTEMPT='3'
$ gh run view "$RUN_ID" --repo "$REPO" --attempt "$ATTEMPT" --verbose
Do not treat the newest attempt as proof an earlier failure was harmless. Compare the attempt number, commit SHA and conclusion in the summary; an attempt that does not exist returns an error rather than silently showing a different one.
5. Retrieve the full or failed-step log
Use --log for the full log of a run or a selected job. Start with a job when you already know where the failure is: the output is smaller:
$ gh run view --repo "$REPO" --job "$JOB_ID" --log
$ gh run view --repo "$REPO" --job "$JOB_ID" --log-failed
--log-failed limits output to logs for failed steps. Omit --job and the command requests failed-step logs across the whole run. The GitHub CLI manual warns that platform limitations can stop some log lines being tied to a step, showing UNKNOWN STEP instead. Missing job logs can also trigger a slower per-job fallback, and more than 25 missing logs fails the operation outright.
Warning
Do not redirect logs into a shared directory. If you need a local copy, use a private temporary directory and remove it after review:
$ LOG_DIR="$(mktemp -d)"
$ gh run view --repo "$REPO" --job "$JOB_ID" --log-failed \
> "$LOG_DIR/failed.log"
$ less "$LOG_DIR/failed.log"
$ rm -rf -- "$LOG_DIR"
That final rm -rf is destructive for the temporary copy and cannot be undone. Run it only after checking the file holds nothing you still need, and never upload it before searching for tokens, passwords, private URLs and personal data.
6. Produce stable machine-readable output
Human summaries are fine at a terminal but fragile in automation. Select named fields with --json, then shape them with --jq:
$ gh run view "$RUN_ID" --repo "$REPO" \
--json databaseId,displayTitle,status,conclusion,headBranch,headSha,url \
--jq '{id: .databaseId, title: .displayTitle, status, conclusion, branch: .headBranch, sha: .headSha, url}'
Omit --jq for the complete JSON record. Available fields include attempt, conclusion, createdAt, databaseId, displayTitle, event, headBranch, headSha, jobs, name, number, startedAt, status, updatedAt, url, workflowDatabaseId and workflowName. Ask only for what the next command needs; it keeps the output easier to audit.
Use a Go template instead when the result needs to be one line of text:
$ gh run view "$RUN_ID" --repo "$REPO" \
--json status,conclusion,headSha \
--template 'status={{.status}} conclusion={{.conclusion}} sha={{.headSha}}\n'
Validate a complex --jq expression against one real response before it goes anywhere near CI. A successful gh run view command only means the view request completed, not that the workflow itself succeeded.
7. Make a shell check fail for a failed run
Add --exit-status when a script must stop on a failed run:
$ if gh run view "$RUN_ID" --repo "$REPO" --exit-status; then
> echo 'run passed or is still pending'
> else
> status=$?
> echo "run failed or could not be read (exit $status)" >&2
> exit "$status"
> fi
The manpage draws the same distinction: a zero status suits a run that is pending or passed. A non-zero status means the run failed, or that the request itself failed. If your automation needs to tell those apart, query --json status,conclusion and handle API errors separately instead of assuming every non-zero result is a workflow conclusion.
8. Open the run in a browser when the terminal is not enough
Use --web to open the selected run in a browser:
$ gh run view "$RUN_ID" --repo "$REPO" --web
This needs a graphical or otherwise configured browser environment, and it is not a substitute for recording the run ID and commit SHA. On a headless host, use --json ... --jq ... and the URL field instead.
Done means
- Checked the version. You confirmed the installed GitHub CLI version and authenticated repository access.
- Picked the right run. You selected a run ID from the intended repository, rather than guessing from a job or pull request number.
- Found the real job ID. You used
--verboseand structuredjobsoutput to identify the correct database ID. - Checked the attempt. You distinguished a re-run attempt from the run's other attempts.
- Handled the log safely. You retrieved only the log scope you needed and treated its content as sensitive.
- Made failure visible. Your script handles
--exit-statusas a failure signal without confusing request errors with workflow conclusions.