GitHub Actions — Conditional Job Execution

Pavel Saman
3 min readSep 9, 2022

Conditional job execution might not be the most used feature in GitHub Actions but it’s worth mentioning, especially because the conditions might be a bit confusing.

Let’s take this workflow as an example:

name: teston:
workflow_dispatch:
jobs:
a:
name: a
runs-on: ubuntu-latest
steps:
- run: echo "A"
- id: set
run: |
echo "::set-output name=run_job_b::yes"
outputs:
run_job_b: ${{ steps.set.outputs.run_job_b }}
b:
name: b
runs-on: ubuntu-latest
needs:
- a
if: needs.a.outputs.run_job_b == 'yes'
steps:
- run: echo "B"
c:
name: c
runs-on: ubuntu-latest
needs:
- a
- b
if: needs.b.result == 'success' || needs.b.result == 'skipped'
steps:
- run: echo "C"

How do you expect the jobs will be executed?

Here are my expectations I had before I actually tested it:

  • job a runs and sets run_job_boutput variable
  • job b depends on a, if a runs without problems (succeeds) and run_job_b variable is set to yes, job b runs as well
  • job c runs only when a succeeds and b secceeds or is skipped

Let’s run the workflow:

Workflow executed
Workflow executed

Looks good.

Let’s run another test, let’s skip job b:

name: teston:
workflow_dispatch:
jobs:
a:
name: a
runs-on: ubuntu-latest
steps:
- run: echo "A"
- id: set
run: |
echo "::set-output name=run_job_b::no"
outputs:
run_job_b: ${{ steps.set.outputs.run_job_b }}
b:
name: b
runs-on: ubuntu-latest
needs:
- a
if: needs.a.outputs.run_job_b == 'yes'
steps:
- run: echo "B"
c:
name: c
runs-on: ubuntu-latest
needs:
- a
- b
if: needs.b.result == 'success' || needs.b.result == 'skipped'
steps:
- run: echo "C"

Job b will be skipped, but I still expected job c to be executed because that’s what the condition says after all:

if: needs.b.result == 'success' || needs.b.result == 'skipped'

This happened:

Job c did not run when job b was skipped
Job c did not run when job b was skipped

Wait, what?

This made me confused for a bit. I tried all sorts of variations of the conditions, e.g.:

if: |
needs.a.result == 'success' &&
(needs.b.result == 'success' || needs.b.result == 'skipped')

I checked the job result statuses.

Eventually I found this “issue”. It turns out that GitHub says in their docs:

You can use the following status check functions as expressions in if conditionals. A default status check of success() is applied unless you include one of these functions.

Where “one of these functions” refers to:

  • sucess()
  • always()
  • cancelled()
  • failure()

That leaves me with something like this:

if: |
always() &&
needs.a.result == 'success' &&
(needs.b.result == 'success' || needs.b.result == 'skipped')

Or more general for my situation:

if: always() && <my-initial-condition>

Not very readable at first glance in my opinion. But when executed, it works:

Job c runs when job b is skipped
Job c runs when job b is skipped

It also works in some other situations:

  • job a succeeds and job b succeeds, job c runs
  • job a fails, job b and c don’t run
  • job a succeeds and job b fails, job c doesn’t run

It took me a while to figure it out, but it seems like it eventually works fine. So beware when building something similar, the behaviour might not be what you initially expect it to be.

--

--