Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXXVII · CI FundamentalsCI Fundamentals

Status checkout — what gets reported back, and who reads it

Foundation⏱ ~17 mingitgh

What you'll learn

  • Define the commit status and the four canonical states (success, failure, pending, error)
  • Identify which systems consume commit status (branch protection, GitOps, PR decoration)
  • Explain why the status is attached to the SHA, not the branch or PR
  • Diagnose why a required status check is reported as missing on a merge

Prerequisites

None — start here.

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

A CI run produces a result, and that result is reported back to the forge as a commit status: a small piece of metadata attached to the exact commit SHA the runner executed against. The status is the lingua franca of the forge - the only signal that branch protection, GitOps controllers, PR decoration, and downstream pipelines all share. Understanding what the status contains, what it does not contain, and which systems read it is the difference between a merge that fails for an obvious reason and a merge that fails for a reason nobody can name.

The commit status, in four states

flowchart LR
    A["Runner finishes"] --> B{"Exit code?"}
    B -->|0| S["Status: success"]
    B -->|non-zero| F["Status: failure"]
    A -.->|runner crashed| E["Status: error"]
    A -.->|in progress| P["Status: pending"]

A commit status has exactly four canonical states:

  • success. The pipeline exited zero. The runner’s last command returned 0. This is the green check on a pull request.
  • failure. The pipeline exited non-zero. At least one step failed. The runner’s last command returned a non-zero exit code.
  • pending. The pipeline is in progress. Branch protection treats pending as “wait”. A merge button while a required check is pending is greyed out.
  • error. The runner could not start the pipeline, or the pipeline itself could not be parsed, or the forge could not reach the runner. This is distinct from failure: failure means the pipeline ran and disagreed with itself; error means the pipeline could not run at all.
gh run list --workflow=build.yml --limit 5

The CLI surfaces the same four states per run.

The status is attached to a SHA, with three fields the consumer cares about:

  • context / name. What the status is for. Branch protection looks for a specific context name; the PR decoration groups checks by context.
  • state. success / failure / pending / error.
  • description / target_url. A short human-readable line and a link to the run UI.

The status does not contain: the diff, the runner’s logs, the secrets the job held, or the dependencies the job used. The status is a verdict, not a record. The record is the run URL in target_url.

Who reads the status

flowchart LR
    C["CI\n(runner)"] -->|"publishes status\nagainst SHA"| S["Commit status\n(forge metadata)"]
    S --> R1["Branch protection\n(blocks merge)"]
    S --> R2["GitOps controller\n(decides to deploy)"]
    S --> R3["PR decoration\n(shows check on PR)"]
    S --> R4["Downstream pipelines\n(next pipeline gates on this one)"]

Four classes of consumer read the status:

  1. Branch protection. The forge’s own merge gate. The rule says “the commit at HEAD must have a successful status with context ci/build before the merge button is enabled.” When the status is missing or failing, the merge is blocked.
  2. GitOps controllers. A Flux or Argo CD controller can read commit status to decide whether a change in the cluster repo is safe to apply. “Apply only when status infra-plan is success on the merge commit.”
  3. PR decoration. The pull request UI. The green checks on the PR conversation tab are status entries; the red X is a status entry with state failure.
  4. Downstream pipelines. A workflow that triggers on another workflow’s success is, at the platform level, reading the status of the upstream run.

All four consumers read the same status. A status is identified by (SHA, context). A status at (wrong SHA, right context) is invisible to branch protection, even if it is green.

From status to consumer: the read path

sequenceDiagram
    participant Runner
    participant Forge
    participant Branch as Branch protection
    participant GitOps as GitOps controller

    Runner->>Forge: report status (sha, context, success)
    Forge->>Forge: store status against SHA
    Branch->>Forge: query (HEAD sha, required contexts)
    Forge->>Branch: status entries
    Branch->>Branch: evaluate rule
    GitOps->>Forge: query (cluster-commit sha, infra-plan context)
    Forge->>GitOps: status entry
    GitOps->>GitOps: decide to apply or wait

Branch protection queries the forge for the status of the required contexts at the SHA that will land on main. GitOps queries the forge for the status of the cluster-commit at its observed SHA. Both queries are reads against the same metadata the runner wrote.

The read path is async. The runner publishes; the consumer queries. There is no push notification from forge to consumer in the standard model. A merge attempt and a GitOps reconcile that happen in the same minute will see the same status, but neither knows about the other’s read.

Reading status from the command line

gh run list --workflow=build.yml --limit 5
# Lists runs with their conclusion: success, failure, cancelled

gh run watch 1234567890 --exit-status
# Blocks until the run completes and prints the final state

gh api repos/$OWNER/$REPO/commits/$SHA/status
# Returns the status object: state, contexts, target_url

The gh api call against the status endpoint is the most direct read. The response includes the four-state field per context, the description, and the target URL.

Production discipline

  1. Report status at the SHA the forge sent. Not the branch ref, not the merge ref, not the local HEAD - the SHA the forge pinned in $GITHUB_SHA.
  2. Verify the context name matches. Branch protection checks for a specific string; the pipeline file emits a specific string. They must be identical.
  3. Treat the status as a verdict, not a record. The run URL is the record; the status is the verdict that drives automation.
  4. Test the read path. A merge attempt against a branch with no required checks is the canonical symptom of a status attached to the wrong SHA.

Cross-course references

  • Linux for Production Sysadmins - Part XXXIV (ConfigMgmt) uses the same status model for package repository publication: the build runs, the status is the package index entry.
  • Ansible for Production Sysadmins - Part XXXVII (RepoArch) uses commit status to gate the release branch merge that publishes the collection.
  • Terraform for Production Sysadmins - Parts IX-XII (State) use the status to gate the apply job on the merge commit; the GitOps controller in the next part reads the same status.

Quiz

Knowledge check · 4 questions

  1. Q1. Branch protection requires a check named 'ci/build'. The pipeline runs and is green on the PR, but the merge button reports 'no required checks'. What is the most likely cause?

  2. Q2. A commit status can be in one of four states: success, failure, pending, or error.

  3. Q3. Name three systems that consume commit status, and explain what each one decides based on the status.

  4. Q4. Diagnose why a GitOps controller refuses to apply a change even though the build pipeline is visibly green on the PR.

    Team T uses Argo CD to apply changes from the cluster repo. Argo CD is configured to require a successful status check 'infra-plan' on the target SHA before applying. The build pipeline runs terraform plan and reports success via the status API. The PR shows the green check. Argo CD reports 'waiting for required status' and never applies.

Passing score: 75%. Answers are checked in this browser.