🛠️

GitHub ActionsのJobスキップの仕様

2023/08/31に公開1

以下のようなワークフローを実行したらどのようになるでしょうか?

name: Dispatch Workflow

on:
  workflow_dispatch:

jobs:
  first_job:
    name: First Job
    runs-on: ubuntu-latest
    if: false
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v3

    - name: Run Shell
      run: echo "Hello World"

  second_job:
    name: Second Job
    runs-on: ubuntu-latest
    needs: first_job
    if: always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled')
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v3

    - name: Run Shell
      run: echo "Hello World"

  third_job:
    name: Third Job
    runs-on: ubuntu-latest
    needs: second_job
    if: success() || failure()
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v3

    - name: Run Shell
      run: echo "Hello World"

third_jobの発動条件はneeds: second_jobif: success() || failure()なので、second_jobの終了を待ってから、second_jobが成功するか失敗したら発動しそうに見えます。
しかし、実際にはスキップされてしまいます。

つまりJobスコープでのif: success()などの条件は一つ前のJobの結果を見ているのではなく、ワークフロー全体を見ているようです。

では、needs.second_job.result == 'success'のように一つ前のJobの結果を直接見るようにしたらどうでしょうか?

name: Dispatch Workflow

on:
  workflow_dispatch:

jobs:
  first_job:
    name: First Job
    runs-on: ubuntu-latest
    if: false
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v3

    - name: Run Shell
      run: echo "Hello World"

  second_job:
    name: Second Job
    runs-on: ubuntu-latest
    needs: first_job
    if: always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled')
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v3

    - name: Run Shell
      run: echo "Hello World"

  third_job:
    name: Third Job
    runs-on: ubuntu-latest
    needs: second_job
    if: needs.second_job.result == 'success' || needs.second_job.result == 'failure'
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v3

    - name: Run Shell
      run: echo "Hello World"

やはりスキップされてしまいました。

全体でスキップされているJobがある場合、そもそもifの条件に入る前にスキップ判定になってしまうようです。なので、always()の条件もつけます。

name: Dispatch Workflow

on:
  workflow_dispatch:

jobs:
  first_job:
    name: First Job
    runs-on: ubuntu-latest
    if: false
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v3

    - name: Run Shell
      run: echo "Hello World"

  second_job:
    name: Second Job
    runs-on: ubuntu-latest
    needs: first_job
    if: always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled')
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v3

    - name: Run Shell
      run: echo "Hello World"

  third_job:
    name: Third Job
    runs-on: ubuntu-latest
    needs: second_job
    if: always() && (needs.second_job.result == 'success' || needs.second_job.result == 'failure')
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v3

    - name: Run Shell
      run: echo "Hello World"

すると、ようやくスキップされずに実行されました。

Jobの発動条件は注意深く指定しないといけませんね。

Discussion

CenturyCentury

always()の挙動について、参考にさせていただきました。ありがとうございます。
ただ、以下の文言については、正確ではないと思われる箇所がありましたので、投稿させていただきます。

全体でスキップされているJobがある場合、そもそもifの条件に入る前にスキップ判定になってしまうようです。

↑上記文言について、正しくは「間接依存も含めたneedsに含まれているjobの中で、でスキップされているJobがある場合」が正しいかと思います。
全体の一部であっても、needsの間接依存にないものに関しては、skipされようが関係ないと思われます。