Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXLVIII · Conditional ExecutionGuards

Conditional fundamentals — when to skip, when to run, and the cost of always-running

Intermediate⏱ ~18 mingit

What you'll learn

  • Recognise that `if:` and `rules:` are guards, not policies
  • Identify three situations where unconditional execution wastes resources
  • Distinguish "skip the job" from "run the job and let it fail"
  • Apply the `always()`, `success()`, and `failure()` result functions to step-level guards

Prerequisites

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 conditional is a guard - a Boolean expression that decides whether a job or step starts at all. It is the first line of triage: does this unit of work have any business running for this event?

When to skip, when to run

Three situations where skipping is correct: the event is not relevant (a push to docs/ triggering a Terraform plan); the trigger already implies the answer (a pull_request from a fork on a public repo carries untrusted code - a deploy step there is a vulnerability, not a conditional); the job is downstream of one that failed (a notification that only matters on test failure should run on if: failure(), not always).

Three situations where running is correct: the job is the answer (a build job on push to main); the result is itself the signal (a lint job that fails on style violations); a downstream consumer needs the artifact regardless of branch.

flowchart LR
    A[Event arrives] --> B{Guard evaluates}
    B -- "true" --> C[Run job or step]
    B -- "false" --> D[Skip]

Reading a guard

jobs:
  plan:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - run: terraform plan

  test:
    runs-on: ubuntu-latest
    steps:
      - run: make test
      - run: upload-coverage
        if: always()
      - run: notify-failure
        if: failure()

The if: value is an expression evaluated against the run context; the job runs when the expression is true. A skipped job is recorded with a neutral status - it does not fail the pipeline, does not block a merge, does not count towards required-checks. The semantic difference between if: false and run: exit 1 is the difference between not applicable and broken. The result functions success(), failure(), always(), cancelled() look at the previous step in the same job, not at the trigger.

Production discipline

  1. Default to running. If you skip a guard, write the comment that explains why.
  2. A guard is a contract. Treat changes to if: clauses as pull-request review items.
  3. Never guard with a side effect. A guard that reaches into an external system turns the pipeline into a dependency on that system.

Cross-course references

  • Ansible for Production Sysadmins - Part XXVI covers the same guards at the playbook-task level.
  • Terraform for Production Sysadmins - Part XXII covers terraform plan against specific resources.

Quiz

Knowledge check · 4 questions

  1. Q1. A pipeline runs five unconditional jobs on every push to every branch. The strongest production argument for adding guards is:

  2. Q2. A skipped job in GitHub Actions is recorded as a failure and blocks required-status checks.

  3. Q3. Name the three GitHub Actions result functions and the appropriate use case for each.

  4. Q4. Diagnose why a `terraform plan` that should be the team's most-watched job has become unreadable.

    The pipeline runs `terraform plan` (9 min warm cache) on every push to every branch in a 30-engineer monorepo. `docs/` commits trigger a full plan that produces no signal because the Terraform module does not depend on `docs/`.

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