if conditional evaluated wrong for contains(needs.*.result, 'failure')
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 6.3k
- Forks
- 1.4k
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 24
Description
Describe the bug
A job-level if conditional isn't evaluated properly when using the contains function and array wildcard syntax.
For example, if contains(needs.*.result, 'failure') evaluates to false, a job with if: always() && contains(needs.*.result, 'failure') will run, but a job with if: always() && (needs.job_a.result == 'failure' || needs.job_b.result == 'failure') won't run.
To Reproduce
- Run a workflow with the following yaml configuration. Examining the output reveals that:
a. Job a fails
b. Job b is skipped
c. Job c is skipped
d. Job d runs (unexpected) even thoughcontains(needs.*.result, 'failure')evaluates to false
jobs:
a:
runs-on: ubuntu-latest
steps:
- run: |
echo "This job fails."
exit 1
b:
runs-on: ubuntu-latest
if: ${{ false }}
needs: a
steps:
- run: echo "This job never happens."
c:
runs-on: ubuntu-latest
if: ${{ false }}
needs: [a, b]
steps:
- run: echo "This job never happens."
d:
runs-on: ubuntu-latest
needs: [b, c]
if: always() && contains(needs.*.result, 'failure')
steps:
- run: |
echo "I don't want this to run unless b or c fails!"
echo ${{ contains(needs.*.result, 'failure') }} # Evaluates to false!

- However, running a workflow with the following yaml has the expected outcome:
a. Job a fails
b. Job b is skipped
c. Job c is skipped
d. Job d is skipped (expected)
jobs:
a:
runs-on: ubuntu-latest
steps:
- run: |
echo "This job fails."
exit 1
b:
runs-on: ubuntu-latest
if: ${{ false }}
needs: a
steps:
- run: echo "This job never happens."
c:
runs-on: ubuntu-latest
if: ${{ false }}
needs: [a, b]
steps:
- run: echo "This job never happens."
d:
runs-on: ubuntu-latest
needs: [b, c]
if: always() && (needs.b.result == 'failure' || needs.c.result == 'failure') # ONLY CHANGE
steps:
- run: |
echo "I don't want this to run unless b or c fails!"
echo ${{ contains(needs.*.result, 'failure') }} # Evaluates to false!

Expected behavior
if: always() && contains(needs.*.result, 'failure') should have the same behavior as if: always() && (needs.b.result == 'failure' || needs.c.result == 'failure'). In other words, if: always() && contains(needs.*.result, 'failure') should evaluate to false and job d should be skipped.
Runner Version and Platform
Version of your runner?
Not sure - running with GitHub Actions (not self-hosted runner)
OS of the machine running the runner?
ubuntu-latest
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the two workflow YAML configurations from the issue and compare job d's conditional behavior when b and c are skipped. Trace the runner's evaluation of always(), contains(), and needs.*.result, then verify that the wildcard expression and explicit result comparisons produce the same skip behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- ci-cd
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100