actions / actions/runner

if conditional evaluated wrong for contains(needs.*.result, 'failure')

Open
#1,540 8 comments 20 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug Runner Bug
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

  1. 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 though contains(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!

image

  1. 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!

image

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.