GitHub Actions · lesson 6 of 7
Beginner
2Intermediate
4Also part of GitHub Actions
5- + Bash Error Handling from Bash
- + CI/CD Pipeline Patterns: Sequential, Parallel, and Staged from DevOps
- + Terraform CI/CD with GitHub Actions (plan/apply) from Terraform
- + A CI Security Gate: Gitleaks, Bandit and pip-audit in One Workflow from DevSecOps (AppSec in the pipeline)
- + Migrating a Jenkins Pipeline to GitHub Actions from Jenkins
if Conditions: Why env.DEPLOY Ran a Step It Was Meant to Skip
env.DEPLOY: false stores the string "false" — and in GitHub Actions expression syntax, any non-empty string is truthy, so if: env.DEPLOY runs the step regardless of what the flag actually says.
By SkillByExample · Published
env.DEPLOY: false looks like it stores a boolean. It stores the string "false" — and that
distinction is exactly where this bug comes from.
The bug: a “false” flag that still runs
# .github/workflows/conditional_bug.yml
name: ConditionalBug
on: [push]
env:
DEPLOY: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Deploy if env says so
if: env.DEPLOY
run: echo "deploying (this should NOT run)"
- name: Always print
run: echo "build finished"
act push -W .github/workflows/conditional_bug.yml -P ubuntu-latest=catthehacker/ubuntu:act-latest
[ConditionalBug/build] ⭐ Run Main Deploy if env says so
[ConditionalBug/build] | deploying (this should NOT run)
[ConditionalBug/build] ✅ Success - Main Deploy if env says so [107.731312ms]
[ConditionalBug/build] ⭐ Run Main Always print
[ConditionalBug/build] | build finished
[ConditionalBug/build] 🏁 Job succeeded
DEPLOY is set to false — and the deploy step ran anyway. env.DEPLOY holds the string
"false", not a boolean, and GitHub Actions expression syntax treats any non-empty string as
truthy. "false" has four characters; it is not empty; therefore if: env.DEPLOY evaluates to
true, regardless of what the string’s actual text says.
The fix: compare the string explicitly
# .github/workflows/conditional_fixed.yml
name: ConditionalFixed
on: [push]
env:
DEPLOY: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Deploy if env says so
if: env.DEPLOY == 'true'
run: echo "deploying (this should NOT run)"
- name: Always print
run: echo "build finished"
act push -W .github/workflows/conditional_fixed.yml -P ubuntu-latest=catthehacker/ubuntu:act-latest
[ConditionalFixed/build] ⭐ Run Main Always print
[ConditionalFixed/build] | build finished
[ConditionalFixed/build] 🏁 Job succeeded
No “Deploy if env says so” step at all this time — env.DEPLOY == 'true' tests the string’s
actual content ("false" does not equal "true") instead of just whether it’s non-empty.
Practice
1. Flip DEPLOY to true and confirm the fixed condition now runs the step.
env:
DEPLOY: true
# ... if: env.DEPLOY == 'true' unchanged
[ConditionalFixed/build] ⭐ Run Main Deploy if env says so
[ConditionalFixed/build] | deploying (this should NOT run)
[ConditionalFixed/build] ⭐ Run Main Always print
[ConditionalFixed/build] | build finished
[ConditionalFixed/build] 🏁 Job succeeded
Now it runs — env.DEPLOY is the string "true", which does equal 'true'. Same fixed
condition, correct behavior in both directions.
2. Confirm an empty string is the one case where bare truthiness testing behaves as expected.
env:
DEPLOY_TARGET: ""
jobs:
build:
steps:
- if: env.DEPLOY_TARGET
run: echo "target is set"
- run: echo "build finished"
[EmptyCheck/build] ⭐ Run Main echo "build finished"
[EmptyCheck/build] | build finished
[EmptyCheck/build] 🏁 Job succeeded
No “target is set” step ran — an empty string is the one value bare truthiness testing
correctly identifies as falsy. This is the legitimate use case for if: env.SOME_VAR on its
own: checking “is this set at all,” not “does this hold the word true.”
3. Combine a condition with && to require two things at once.
env:
DEPLOY: true
ENVIRONMENT: staging
jobs:
build:
steps:
- if: env.DEPLOY == 'true' && env.ENVIRONMENT == 'production'
run: echo "deploying to production"
- run: echo "build finished"
[Combined/build] ⭐ Run Main echo "build finished"
[Combined/build] | build finished
[Combined/build] 🏁 Job succeeded
No production deploy step ran, even with DEPLOY: true — ENVIRONMENT is staging, not
production, so the && combination correctly requires both halves to be true.
Next: caching dependencies, and measuring the real speedup it produces.