Mastering Conditional Execution in GitHub Actions
In a professional CI/CD pipeline, you rarely want every single job or step to run every time a workflow is triggered. For example, you might want to run tests on every pull request but only deploy to production when code is merged into the main branch. This is where conditional execution comes into play.
GitHub Actions provides the if conditional to control whether a job or step should run based on specific criteria. By mastering these conditions, you can save compute minutes, speed up your feedback loop, and prevent accidental deployments.
Understanding the "if" Conditional
The if keyword can be applied at two levels: the Job level and the Step level. When an if expression evaluates to true, the job or step runs. If it evaluates to false, it is skipped entirely.
[Event Triggered]
|
v
[Evaluate Job Condition] --(False)--> [Skip Job]
| (True)
v
[Evaluate Step Condition] --(False)--> [Skip Step]
| (True)
v
[Execute Step Logic]
Common Expressions and Syntax
GitHub Actions uses a specific expression syntax for conditions. While you usually wrap expressions in ${{ }} elsewhere in a workflow, the if conditional automatically evaluates its content as an expression, so the curly braces are optional.
- github.ref: The branch or tag ref that triggered the workflow (e.g.,
refs/heads/main). - github.event_name: The name of the event (e.g.,
push,pull_request). - success(): Returns true if no previous step has failed (default behavior).
- failure(): Returns true if any previous step of a job has failed.
- always(): Forces a step to run even if previous steps failed or were cancelled.
- cancelled(): Returns true if the workflow was manually cancelled.
Practical Code Examples
Example 1: Running a Step Only on the Main Branch
This is the most common use case. You might want to build your application on every branch but only upload the artifact if you are on the main branch.
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Application
run: npm run build
- name: Deploy to Production
if: github.ref == 'refs/heads/main'
run: npm run deploy
Example 2: Running a Job Based on Event Type
You can prevent an entire job from running unless the event is a pull_request. This is useful for expensive security scans that you only want to run during code reviews.
jobs:
security-scan:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm audit
Example 3: Error Handling with failure()
If a build fails, you might want to send a notification to a Slack channel. The failure() function ensures this step only triggers when something goes wrong.
- name: Notify Slack on Failure
if: failure()
run: curl -X POST -d "text=Build Failed!" ${{ secrets.SLACK_WEBHOOK }}
Real-World Use Cases
- Environment Specifics: Deploying to a "Staging" environment if the branch is
developand to "Production" if the branch ismain. - Selective Testing: Running heavy integration tests only when a specific label (like "run-ui-tests") is added to a Pull Request.
- Cleanup Operations: Using
always()to ensure that temporary cloud resources or Docker containers are torn down, regardless of whether the tests passed or failed. - Skipping Documentation: Using conditions to skip documentation site builds if only the
/srcfolder was modified and the/docsfolder remained unchanged.
Common Mistakes to Avoid
- Incorrect Ref Format: Forgetting that
github.refincludes the full prefix. Userefs/heads/maininstead of justmain, or usegithub.ref_namefor the short version. - Using Quotes for Booleans: Writing
if: 'true'(string) instead ofif: true(boolean). - Context Availability: Trying to use
envvariables in a job-levelifcondition. Job-level conditions are evaluated before environment variables are initialized. - Overusing always(): Using
always()on a step that depends on a previous step's output. If the previous step failed, the output might be missing, causing the "always" step to crash.
Interview Notes for Developers
- Question: How do you make a step run even if the previous step fails?
- Answer: Use the
if: failure()orif: always()status check functions. - Question: Is the
${{ }}syntax required in anifblock? - Answer: No, it is optional because GitHub Actions automatically treats the content of an
ifkey as an expression. - Question: How can you check if a workflow was triggered by a specific user?
- Answer: You can use the
github.actorcontext, for example:if: github.actor == 'octocat'.
Summary
Conditional execution is a fundamental pillar of efficient CI/CD. By using the if keyword at the job or step level, combined with GitHub's rich set of contexts and status check functions, you can create highly dynamic and intelligent workflows. Remember to use github.ref for branch filtering, failure() for error handling, and always() for essential cleanup tasks. This concludes Topic 11: Conditional Execution of Jobs and Steps. In the next part of our series, we will explore how to use matrix builds to run tests across multiple environments simultaneously.