Jobs skipped when NEEDS job ran successfully
Describe the bug One or more jobs are skipped in a workflow when run after successfully running a job matching following conditions:
- Job has NEEDS dependency on one or more preceding jobs. (i.e. needs: [job1, job2])
- Job has if condition specifying behavior when NEEDS jobs are skipped. (i.e. if: always() && needs.job1.result == 'skipped')
- Preceding job is skipped (i.e. job1)
To Reproduce Create a workflow as follows:
name: Skipped Job Test
on:
workflow_dispatch:
jobs:
Skipped-Job:
name: skipped job
runs-on: [self-hosted]
if: 1 != 1
steps:
- name: write to console 1
run: Write-Host "I should be skipped"
Needs-Job:
name: needs conditional job
needs: Skipped-Job
if: always() && needs.Skipped-Job.result != 'failed' # this should run eventhough needs the skipped-job
runs-on: [self-hosted]
steps:
- name: write to console 2
run: Write-Host "Needs-Job completed"
Next:
name: Next job
needs: Needs-Job
# if: always() && needs.Needs-Job.result == 'success' # uncommenting this condition causes the job to not be skipped
runs-on: [self-hosted]
steps:
- name: write to console 3
run: Write-Host "I should print whenever Needs-Job succeeds"
Expected behavior I'd expect the job "Next" to run, because the preceding job " Needs-Job" is completed successfully and is the only condition for this job to run (it needs Needs-Job), it is however skipped. uncommenting the 3rd line in that job is a fix, but this feels hacky and shouldn't be needed.
Runner Version and Platform
Current runner version: '2.298.2'
Running on: Windows Server 2022 Standard Version 21H2 OS build 20348.1006
I believe the status is the wrong word - try ‘failure’ instead of ‘failed’.
Hi @GennaroGreg,
Thanks for the reply, the status 'failed' might indeed be the wrong word, however it shouldn't matter for job "Needs-Job" as the check is if it's not equal to, and since "Skipped-Job" is always skipped the status will always be 'skipped' (right?)
I've tested just to be sure:

I'm having a similar problem. Where a job earlier in the chain is skipped causes all down stream jobs to be skipped even if they don't explicitly state that they need that job to continue.
Following pseudo code demonstrates what I'm seeing.
jobs:
get-semaphor:
runs-on: ubuntu-latest
steps:
- name: get-some-semaphor
- run: echo "semaphor=false" >> $OUTPUT
check-semaphor:
runs-on: ubntu-latest
needs: get-semaphor
if: needs.get-semaphor.output.semaphor == true
steps:
- name: Do something here
do-task-a:
runs-on: ubntu-latest
needs: ['check-semaphor', 'get-semaphor']
if: alsways && (needs.check-semaphor.result == skipped || needs.check-semaphor.result == success) && needs.get-semaphor.result == success
steps:
- name: do something
# This task-b will never run if check-semaphor is skipped or fails. Even though A succeeds.
do-task-b-aslong-as-a-works:
needs: do-task-a
steps:
- name: do something
@DameonSmith IIRC you'd need to add if: always() && needs.do-task-a.result == success in this case. Once a job in the workflow is failed or skipped, you need to overwrite the conditional for all subsequent jobs...
Hi @mulhotavares, thanks for the comment, I do know this works but I consider it a workaround. I could be wrong however and this could be intended behavior, do you perhaps have a link to Github documentation that explains this as intended behavior?
It feels like a bug that the run condition on job A would impact job C that has no condition on the skipped job (only on job B which is run and successful)
Hi @niekvanderreest. In terms of the documentation, I haven't found that mentioned directly, but the docs do state:
A default status check of success() is applied unless you include one of these functions.
And
success Returns true when none of the previous steps have failed or been canceled.
(both quotes from this: https://docs.github.com/en/actions/learn-github-actions/expressions#job-status-check-functions)
So it is respecting both of those statementes, but I agree this looks awkward. Anyways, I was just trying to help, especially as others could find this in Google like I did. 😄
Experienced this as well. Unless explicitly documented, this feels like a bug. Current behavior is unintuitive as well as cumbersome to develop, since one has to push the if clause to all descendent jobs just because of one skipped ancestor.
This is really weird behaviour. As noted here this definitely seems to be a bug (why are we all just running into this now? did everyone suddenly decide to use job ifs?).
A job being skipped seems to be treated as failed, so success() doesn't work for follow on jobs. As documented in the discussion, you should instead use ! failure() which works a treat, but you then have to propagate it to all descendant jobs even if they don't directly depend on the skipped job, which is very confusing.
You should fix success() to not include skipped jobs, as skipped really should mean "this wasn't needed", not "this failed". And needs checks should only occur on the explicitly listed jobs, not ancestors of those jobs.
I have a workflow skips or cancels as soon as it is executed
@jjoseph456 , I think your workflow has an execution order issue, the first job has a needs specifying the second job. Switching position of first and second job should fix this.
I'm no expert at this field, most of the workflow I worked on have a strict order of execution.
@niekvanderreest thanks will try that
I'm having a similar problem. Where a job earlier in the chain is skipped causes all down stream jobs to be skipped even if they don't explicitly state that they need that job to continue.
Following pseudo code demonstrates what I'm seeing.
jobs: get-semaphor: runs-on: ubuntu-latest steps: - name: get-some-semaphor - run: echo "semaphor=false" >> $OUTPUT check-semaphor: runs-on: ubntu-latest needs: get-semaphor if: needs.get-semaphor.output.semaphor == true steps: - name: Do something here do-task-a: runs-on: ubntu-latest needs: ['check-semaphor', 'get-semaphor'] if: alsways && (needs.check-semaphor.result == skipped || needs.check-semaphor.result == success) && needs.get-semaphor.result == success steps: - name: do something # This task-b will never run if check-semaphor is skipped or fails. Even though A succeeds. do-task-b-aslong-as-a-works: needs: do-task-a steps: - name: do something
having the same issue, once always() used in job1, the next job that needs it will not be executed even if the job1 succeeds
I'm having the similar problem, can someone help?
name: Release
on:
push:
branches:
- main
pull_request:
branches:
- main
type: [closed]
jobs:
release:
if: github.event.pull_request.merged == true
name: Release
runs-on: ubuntu-20.04
steps:
- name: Checkout source code
uses: actions/checkout@v3
- name: Cache node_modules
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-node-
- name: Install the dependencies
run: npm ci --legacy-peer-deps
- name: Run release
run: npm run release --ci
- name: End message
run: echo "All done!"
Hello, I had the same problem. The workaround proposed by @mulhotavares is working, but I agree with @niekvanderreest saying this is a workaround and should be solved.
The documentation says that a success() condition is applied by default, and that this function Returns true when none of the previous steps have failed or been canceled. --> so it should return true when the job is skipped.
Moreover, the uses of success() and needs.job-id.result == success are inconsistent, because success() can return false when needs.job-id.result == success returns true, wherease it looks like the same meaning. See below :
# This is working : the job3 is executed
job3:
needs: [job1, job2]
if: always() && (needs.job1.result == 'success' || needs.job2.result == 'success')
versus
# This is not working : the job3 is skipped (documentation says that a success() function is applied by default
job3:
needs: [job1, job2]
if: (needs.job1.result == 'success' || needs.job2.result == 'success')
Hello, does anyone have any news on this subject?
Any news on this?
FWIW, I have found that it works well to use an if statement like the following for all jobs that are dependent, either directly or indirectly, on one or more jobs that may be skipped:
if: ${{ !failure() && !cancelled() }}
I've had some success working around this by using sub-workflows, move the optional job into the sub-workflow and then the main workflow can use the needs as normal and not need the horrible if conditions on all subsequent jobs. The jobs in the sub-workflow still need the if statements on subsequent jobs but it can still reduce the amount needed depending on your workflows. I think the sub-worlflow still needs at least on successful (not skipped) job to run within it but I've not tested this
I cannot believe this is still an active issue. I just ran into this again.
any solutions for this yet apart from introducing sub-workflows? the if conditionals are becoming even more inflated if there are multiple jobs that can be potentially skipped..
Bumping up for visibility as I ran into a similar issue today and came here searching!
I ran into this today and it's really annoying.. i'm coming across strange characteristics of gh actions more and more often...
I'm having the same problem here, we haven't had any updates on this yet?