GitHub Actions のワークフローがずっと「queued」ステータスのままになっていたので、GitHub のページ上からキャンセルボタンを押したところ、success と表示されるものの実際にはワークフローがキャンセルせずに queued のままになってしまう事象が発生しました。
ちなみにそのときのワークフロー定義ファイルは以下のような感じでした。
steps:
...
- name: The job that runs always
if: ${{ always() }}
この always() が曲者でした 🫠
強制的にワークフローをキャンセルする方法 #
always() の詳細は後回しにするとして、このような状態に陥ったときにも強制的にワークフローをキャンセルするには GitHub CLI を使うと良いです。
gh run list -R {リポジトリ名} でワークフローの一覧を取得し、キャンセルしたいワークフローの ID を確認します。STATUS が * がら queued のワークフローです。
gh run list -R my-organization/my-repository
STATUS TITLE WORKFLOW BRANCH EVENT ID ELAPSED AGE
* new-mypage my-validate feat/new-mypage pull_request 19259302212 42m56s about 42 minutes ago
* new-mypage my-validate feat/new-mypage pull_request 19255928662 3h31m31s about 3 hours ago
X new-mypage my-validate feat/new-mypage pull_request 19250659802 3m41s about 8 hours ago
X my-cron-validation cron-validate main schedule 19207217714 1s about 1 day ago
ワークフロー ID が分かったら gh run cancel --force コマンドで強制キャンセルできます。以下の例では 2 つのワークフローを強制キャンセルしています。
gh run cancel --force 19259302212 -R my-organization/my-repository
> ✓ Request to force cancel workflow 19259302212 submitted.
gh run cancel --force 19255928662 -R my-organization/my-repository
> ✓ Request to force cancel workflow 19255928662 submitted.
このあとでブラウザでワークフローのページをリロードすると、ちゃんとキャンセル表示に変わっているはずです。
if: ${{ always() }} について
#
always() は本当にオールウェイズです。たとえジョブがキャンセルされた場合でも true を返します。つまり always() が指定されたジョブやステップは、ワークフローがキャンセルされた場合でも実行され続けます。そういう意味で always() はキャンセル不可能です。
Causes the step to always execute, and returns true, even when canceled. The always expression is best used at the step level or on tasks that you expect to run even when a job is canceled. For example, you can use always to send logs even when a job is canceled.
https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#always
ほとんどの場合において、always() を使う意図は「依存するジョブ/ステップが失敗した場合であっても実行させたい」というものだと思います。その場合は if: ${{ always() }} ではなく if: ${{ !cancelled() }} を使いましょう。
steps:
...
- name: The job that runs always but can be cancelled
if: ${{ !cancelled() }}
if: ${{ !cancelled() }} はどういう意味かというと、「キャンセルされない限り実行する」です。さらに書き加えるならば「(依存するジョブが成功していようが失敗していようが)自身がキャンセルされない限りは実行する」です。要するにキャンセル可能ではありつつも、前のジョブにかかわらず常に実行するという挙動です。
GitHub Docs の always() にも以下の記載があります。
Warning
Avoid using always for any task that could suffer from a critical failure, for example: getting sources, otherwise the workflow may hang until it times out. If you want to run a job or step regardless of its success or failure, use the recommended alternative: if: ${{ !cancelled() }}
https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#always