タイムアウトと手動キャンセルを区別したかった [GitHub Actions]

2022/02/23に公開

概要

if: cancelled() でタイムアウトと手動キャンセルを区別したかったのですが、結論から言うと完全に区別はできなさそうです。
Actions タブの UI 上は区別できるのですが(下記画像の×がタイムアウト、!が手動キャンセル)、フィルタで Status: timed out を指定しても何も引っかからないので公式対応待ちです。

それでもなんとなく区別する方法を以下に記載しておきます。

ワークフロー

.github/workflows/timeout.yml
name: Timeout

on:
  push:
    paths:
      - .github/workflows/timeout.yml
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-20.04
    timeout-minutes: 1
    env:
      GH_TOKEN: ${{ github.token }}
      GH_REPO: ${{ github.repository }}

    steps:
      - run: sleep 60
      - run: |
          timeout_seconds=$(gh workflow view "$GITHUB_WORKFLOW" --yaml \
            | yq '.jobs.build.timeout-minutes * 60')
          created_at=$(gh run view $GITHUB_RUN_ID --json createdAt,updatedAt \
            --jq '.createdAt | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime')
          now=$(date '+%s')
          if ((now > created_at + timeout_seconds)); then
            echo 'timed out'
          fi
        if: cancelled()

https://zenn.dev/snowcait/articles/1ae73059a5ea4d
の応用です。
ワークフローの設定からタイムアウトの時間を取得して開始時刻と現在時刻の差から実行時間を計算しています。

上のコードに残骸が残っていますが updatedAt ではうまく判定できませんでした。

ただし中断処理にそこそこ時間がかかるようで完全には判定できません。
タイムアウト直前に手動キャンセルするとタイムアウトとして判定されてしまいます。

実行中でなければアノテーションからも判断可能

実行が終わった後でなら以下のように情報が取得できます。

$ gh run view $GITHUB_RUN_ID

X main Self workflow · 1886051801
Triggered via push about 2 minutes ago

JOBS
X build in 1m6s (ID 5300127467)

ANNOTATIONS
X The job running on runner Hosted Agent has exceeded the maximum execution time of 1 minute.
build: .github#1

X The operation was canceled.
build: .github#1


To see what failed, try: gh run view 1886051801 --log-failed
View this run on GitHub: https://github.com/SnowCait/actions-sandbox/actions/runs/1886051801

全体の YAML は省略しますがこういう形で判定できると思います。
実行中にアノテーションは取得できなかったので別ワークフローで判定する場合にのみ使用可能です。

タイムアウト
if gh run view $GITHUB_RUN_ID | grep 'has exceeded the maximum execution time of'; then
  echo 'timed out'
fi
手動キャンセル以外
if ! gh run view $GITHUB_RUN_ID | grep 'The run was canceled by'; then
  echo 'timed out'
fi

API をたどることもできます。

> gh api -X GET repos/SnowCait/actions-sandbox/actions/runs/1885921685 --jq '.check_suite_url'
https://api.github.com/repos/SnowCait/actions-sandbox/check-suites/5412681253
> gh api -X GET repos/SnowCait/actions-sandbox/check-suites/5412681253 --jq '.check_runs_url'                                                                          
https://api.github.com/repos/SnowCait/actions-sandbox/check-suites/5412681253/check-runs
> gh api -X GET repos/SnowCait/actions-sandbox/check-suites/5412681253/check-runs --jq '.check_runs[].output.annotations_url'
https://api.github.com/repos/SnowCait/actions-sandbox/check-runs/5299800073/annotations
> gh api -X GET repos/SnowCait/actions-sandbox/check-runs/5299800073/annotations --jq '.[].message'
The run was canceled by @SnowCait.
The operation was canceled.

ただしここの文言も将来的に変更される可能性が高いので完全とは言えません。
実際 canceledcancelled に統一する PR があります。

Discussion