How to cancel remaining running jobs if one of the parallel jobs failed?

Hi there,

I have 3 jobs on my workflow that run in parallel, and then I have a 4th and final job that depends on those 3, like this:

jobs:
  pre-job-a:
    ...
  pre-job-b:
    ...
  pre-job-c:
    ...
  final-job:
    needs:
      - job-a
      - job-b
      - job-c

If one of the pre-* jobs fail, final-job won’t run, it only runs if all pre-* jobs complete. Great, so far so good.

The problem is that one of the pre-* jobs is much faster than the others, and if that job fails for some reason, the other pre-* jobs will keep running until they finish, and the workflow itself will not be cancelled until all the final-job job dependencies are either finished.

Is it possible to solve this problem without making things too complicated with lots of extra steps/jobs or additional workflows?

1 Like

There is no direct way to do that. But you could convert the first three jobs into matrix form if possible and use fail-fast, which basically cancels the rest of the jobs if one job fails.

1 Like

Not sure how would I do that with a matrix, the 3 jobs I have are somewhat different (static type checks, unit testing, and build).

Here’s the workaround I ended up using:

jobs:
    first-job:
        (...)
    post-first-job:
        runs-on: ubuntu-latest
        if: ${{ failure() }}
        needs:
            - first-job
        steps:
            - name: Cancel current workflow run
              uses: actions/github-script@v4
              with:
                  script: |
                      github.actions.cancelWorkflowRun({
                          owner: context.repo.owner,
                          repo: context.repo.repo,
                          run_id: context.runId
                      })
    second-job:
        (...)
    post-second-job:
        runs-on: ubuntu-latest
        if: ${{ failure() }}
        needs:
            - second-job
        steps:
            - name: Cancel current workflow run
              uses: actions/github-script@v4
              with:
                  script: |
                      github.actions.cancelWorkflowRun({
                          owner: context.repo.owner,
                          repo: context.repo.repo,
                          run_id: context.runId
                      })
    third-and-final-job:
        runs-on: ubuntu-latest
        needs: [first-job, second-job]

It’s a bit verbose, but gets the job done, until GitHub adds an option for this.

Cool. I meant something like below.

Note: matrix jobs will have fail-fast as true by default. So if any of the matrix jobs fails, rest of the jobs will be canceled.

jobs:
  pre-jobs:
    strategy:
      matrix:
        type: ['first', 'second', 'third']
      
    runs-on: ubuntu-latest

    steps:
      - name: first-step1
        if: matrix.type == 'first'
        run: echo Hello, world!; sleep 30
      - name: first-step2
        if: matrix.type == 'first'
        run: echo Hello, world!; sleep 30
      - name: second-step1
        if: matrix.type == 'second'
        run: echo Hello, world!; sleep 30
      - name: second-step2
        if: matrix.type == 'second'
        run: echo Hello, world!; sleep 30
      - name: third-step1
        if: matrix.type == 'third'
        run: echo Hello, world!; sleep 5
      - name: third-step2
        if: matrix.type == 'third'
        run: echo Hello, world!; error
  final-job:
    needs: pre-jobs
    runs-on: ubuntu-latest
    
    steps:
      - name: final-step
        run: echo Hello, world!; sleep 5