270

I'm trying to follow an example Github has for testing my build with github actions, and then compressing the test results and uploading them as an artifact. https://help.github.com/en/actions/automating-your-workflow-with-github-actions/persisting-workflow-data-using-artifacts#uploading-build-and-test-artifacts

I'm having trouble with what to do when my tests fail though. This is my action. When my tests pass everything works great, my results are zipped an exported as an artifact, but if my tests fail, it stops the rest of the steps in the job, so my results never get published.
github-ci-result
I tried adding the continue-on-error: true https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error
This makes it continue after it fails and uploads my test results. but then the job is marked as passed, even though my test step failed. Is there some way to have it upload my artifact even if a step fails, while still marking the overall job as failed?

name: CI
on:
  pull_request:
    branches:
    - master
  push:
    branches:
      - master

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1    
    - name: Test App
      run: ./gradlew test

    - name: Archive Rest Results
      uses: actions/upload-artifact@v1
      with:
        name: test-results
        path: app/build/reports/tests
Tomer Shemesh
  • 10,278
  • 4
  • 21
  • 44
  • You may also want to look at `continue-on-error`. I don't much experience with it, but if you're looking to essentially not worry about an error in a particular `job` or `step`, you can use `continue-on-error: true` to allow for failures and not mark the whole `job` / `workflow` as a failure. – Joshua Pinter Sep 13 '21 at 14:35

9 Answers9

358

You can add

if: always()

to your step to have it run even if a previous step fails https://docs.github.com/en/actions/learn-github-actions/expressions#status-check-functions

so for a single step it would look like this:

steps:
- name: Build App
  run: ./build.sh

- name: Archive Test Results
  if: always()
  uses: actions/upload-artifact@v1
  with:
    name: test-results
    path: app/build

Or you can add it to a job:

jobs:
  job1:
  job2:
    needs: job1
  job3:
    if: always()
    needs: [job1, job2]

Additionally, as pointed out below, putting always() will cause the function to run even if the build is canceled. If you don't want the function to run when you manually cancel a job, you can instead put:

if: success() || failure()

or

if: '!cancelled()'

(Quotes are needed so that !cancelled() is not interpreted as a YAML tag.)

Likewise, if you want to run a function ONLY when something has failed, you can put:

if: failure()

Also, as mentioned in the comments, if a status check function is not used in if, like

if: true

the result will (perhaps confusingly) behave like

if: success() && true

This is documented in Expressions - GitHub Docs:

Status check functions

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.

mxxk
  • 9,514
  • 5
  • 38
  • 46
Tomer Shemesh
  • 10,278
  • 4
  • 21
  • 44
  • 46
    Note that it seems that `if: success()` is always implied unless you specify `always()` or `failure()` - even if you set e.g. `if: true`. This means that (perhaps surprisingly) `if: x` is different from `if: always() && x` in that the first will not run if previous steps have failed or the job was cancelled. – coldfix Sep 05 '20 at 08:44
  • 9
    One note from the docs: "When you use expressions in an if conditional, you may omit the expression syntax (`${{ }}`) because GitHub automatically evaluates the if conditional as an expression." In other words, one can simply use: `if: always()`. – Taylor D. Edmiston Jan 25 '21 at 12:09
  • 3
    I submitted a PR to the GitHub docs to clarify this: https://github.com/github/docs/pull/8411 – Vladimir Panteleev Jul 29 '21 at 08:48
  • 5
    Note that this has the unwanted side effect that you can no longer manually cancel your workflow: https://docs.github.com/en/actions/managing-workflow-runs/canceling-a-workflow#steps-github-takes-to-cancel-a-workflow-run – depoulo Feb 03 '22 at 14:32
  • 22
    Contrary to `if: always()`, `if: success() || failure()` keeps the ability to cancel builds while still achieving the OPs goal. – depoulo Mar 04 '22 at 10:43
  • This doesn't seem to work for job matrices, i.e., when using `strategy`. I tried putting `if: success() || failure()` after every step in the job matrix, and the workflow is still halted after the first few jobs fail. – CRoemheld Apr 15 '23 at 17:22
  • For people who want do achieve a similar result for job matrices, use [fail-fast](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategyfail-fast), as seen here: https://stackoverflow.com/questions/70830094 – CRoemheld Apr 15 '23 at 17:38
57

Other way, you can add continue-on-error: true. Look like

- name: Job fail
  continue-on-error: true
  run |
    exit 1
- name: Next job
  run |
    echo Hello

Read more in here.

Khai Vu
  • 1,280
  • 10
  • 9
  • 31
    I described that solution in the original question, but this doesn't work in my case, because with continue-on-error, a failed task will still cause the overall build to pass. And i still wanted failed builds to be marked as failed, while still running this task regardless of the previous tasks. This solution would work if you dont care about the overall build status though. – Tomer Shemesh Nov 21 '21 at 21:57
  • 3
    @TomerShemesh It's the solution to what I was googling for though! – Explosion Dec 16 '21 at 13:44
  • @TomerShemesh I have the exact same problem but `if: always` and `continue-on-error: true` both didn't work for me. did you use `if: always` in every skipped step in case of test failure? – Rsaleh Jan 07 '22 at 08:54
  • 1
    @Rsaleh Yes, i added if:always to every step I needed to run even when there was a failure. – Tomer Shemesh Jan 07 '22 at 17:09
27

run a github-actions step, even if the previous step fails

If you only need to execute the step if it succeeds or fails, then:

steps:
- name: Build App
  run: ./build.sh

- name: Archive Test Results
  if: success() || failure()
  uses: actions/upload-artifact@v1
  with:
    name: test-results
    path: app/build

Why use success() || failure() instead of always()?

Reading the Status check functions documentation on Github:

always

Causes the step to always execute, and returns true, even when canceled. A job or step will not run when a critical failure prevents the task from running. For example, if getting sources failed.

Which means the job will run even when it gets cancelled, if that's what you want, then go ahead. Otherwise, success() || failure() would be more suitable.

Note - The documentation made clear thanks to Vladimir Panteleev in which he submitted the following PR: Github Docs PR #8411

u-ways
  • 6,136
  • 5
  • 31
  • 47
  • 1
    Just to give credit... The "Causes the step to always execute" wording in the documentation was added by Vladimir Panteleev in response to this exact SO question - see his comment in the accepted answer above that links to his PR: https://github.com/github/docs/pull/8411/files – Matt Oct 18 '22 at 14:54
12

Addon: if you have following sitution. 2 steps i.e. build > deploy and in some cases i.e. workflow_dispatch with input parameters you might want to skip build and proceed with deploy. At the same time you might want deploy to be skipped, when build failed.
Logically that would be something like skipped or not failed as deploy conditional.
if: always() will not work, cause it will always trigger deploy, even if build failed.
Solution is pretty simple:
if: ${{ !failure() }}
Mind that you cannot skip brackets when negating in if:, cause it reports syntax error.

shemekh
  • 426
  • 5
  • 14
  • `if` implicitly adds brackets. The problem is that `!` is not allowed to start an unquoted string in yaml. Instead, `if: '!failure()'` or `if: "!failure()"` work. – Cameron Tacklind Feb 01 '23 at 05:08
10

The other answers here are great and work, but you might want a little more granularity.

For instance, ./upload only if ./test ran, even if it failed.
However, if something else failed and prevented the tests from running, don't upload.

      # ... Other steps
      - run: ./test
        id: test
      - run: ./upload
        if: success() || steps.test.conclusion == 'failure'

steps.*.conclusion will be success, failure, cancelled, or skipped.
success or failure indicate the step ran. cancelled or skipped means it didn't.

Note there is an important caveat that you must test at least one success() or failure() in if.
if: steps.test.conclusion == 'success' || steps.test.conclusion == 'failure' won't work as expected.

Cameron Tacklind
  • 5,764
  • 1
  • 36
  • 45
4

Instead of

if: success() || failure()

I suggest to use

if: ${{ ! cancelled() }}

to run a step "always, except when the CI run was cancelled". My version is more concise and in my view less awkward.

Reference: https://docs.github.com/en/actions/learn-github-actions/expressions#status-check-functions

user7610
  • 25,267
  • 15
  • 124
  • 150
2

In case anyone is wondering, you can combine always() with another condition to perform the step if that condition holds true even if there is a failure before that step is processed.

    steps:

      - name: Exit with error
        run: |
          echo "Erroring"
          exit 1

      - name: Output report 1
        if: true
        run: |
          echo "Report 1"

      - name: Output report 2
        if: always() && true
        run: |
          echo "Report 2"

Gives this output: github workflow report

Joman68
  • 2,248
  • 3
  • 34
  • 36
0

The .status property does not work for me, I now use the new API to access the outcome of a step. The new approach allows you to handle step failures more effectively.

To achieve this, you can use the if conditional statement along with the failure() expression. Here's an example of how you can run a subsequent step even if the previous step fails:

jobs:
  my-job:
    runs-on: ubuntu-latest
    steps:
      - name: Step 1
        id: demo
        run: echo "This step will fail" && exit 1
      
      - name: Step 2
        if: ${{ failure() && steps.demo.conclusion == 'failure' }}
        run: echo "This step will run even if the previous step fails"

In the above example, Step 2 will execute regardless of the success or failure of Step 1. The if: ${{ failure() }} condition checks if the previous step failed and allows Step 2 to proceed.

You can refer to the GitHub Actions documentation for more details on using conditions to handle step failures.

Arlind Hajredinaj
  • 8,380
  • 3
  • 30
  • 45
-6

you can add || true to your command. example:

 jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1    
    - name: Test App
      run: ./gradlew test || true
Pawan Jenu
  • 19
  • 3
  • 4
    This is not a great solution because the tests' step will always be marked as pass, even if the test fails. That is not the outcome one usually wants. You still want the step itself to fail and mark the build itself as failed, but still want to run a task like archiving and publishing the results of the tests. Which you want to do regardless of if the test step itself passes or not. Doing your suggestion would cause all builds to always pass regardless of failing tests. – Tomer Shemesh Jan 19 '22 at 02:57