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.
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.
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
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.
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.
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.
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.
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.
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.
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.
--verbose and structured jobs output to identify the correct database ID.--exit-status as a failure signal without confusing request errors with workflow conclusions.