GitHub Actions workflow 被跳過後卡在 Pending?先處理 required check
GitHub Actions workflow 明明沒有失敗,Pull Request 卻一直顯示 Waiting for status to be reported 或 Pending,最常見的原因不是 runner 壞掉,而是 workflow 根本沒有被觸發。當這個 workflow 又被分支保護規則列為 required check,GitHub 就沒有可供合併判斷的結果。
這篇聚焦在「被跳過的檢查」:怎麼分辨 workflow-level filter、job-level if 和 merge queue 的差異,以及如何把 required check 設計成每次都有可判讀的結果。若你的問題是同一個部署 workflow 被多次提交互相取消,則是另一個 GitHub Actions concurrency queue 設定問題。
先分清楚是 workflow 被跳過,還是 job 被跳過
GitHub 官方把這幾種結果分得很清楚:
| 發生位置 | 常見設定 | PR 上看到的結果 | 對 required check 的影響 |
|---|---|---|---|
| workflow 觸發前 | paths、paths-ignore、branches、branches-ignore | workflow 沒有 run | 相關 check 可能停在 Pending,阻擋合併 |
| commit message | [skip ci]、[skip actions] 等 | workflow 沒有 run | 相關 check 可能停在 Pending |
| workflow 裡的 job | jobs.<job_id>.if | job 顯示 skipped | 官方文件把這類結果視為 Success |
needs 相依 job | 前置 job 失敗 | 後續 job skipped | 依賴鏈可能沒有產生你預期的 required 結果 |
這個差異很重要。把 if 從 job 移到 on.pull_request.paths,看起來只是把條件往上移一層,實際上卻會讓整個 workflow 消失。GitHub 的保護規則無法把「不存在的 workflow run」當成成功。
先查三個地方,不要重跑一個不存在的 run
遇到 Pending 時,我會按照這個順序查:
- PR 的 Checks 頁面:確認缺少的是哪一個 check 名稱,以及它要套用在哪個 commit SHA。
- 分支保護或 ruleset:確認這個 check 是否被列為 required,是否還要求 branch 必須先更新到最新 base branch。
- workflow 的
on區段:搜尋paths、paths-ignore、branches、branches-ignore,再看最近的 commit message 是否包含 skip 指令。
可以先在 repository 根目錄列出可能的觸發條件:
rg -n \ '(^|[[:space:]])(paths|paths-ignore|branches|branches-ignore|pull_request|push|merge_group):|skip (ci|actions)' \ .github/workflows .github 2>/dev/null這個搜尋只協助定位,不會告訴你某個 PR 的檔案是否真的符合 glob。最後仍要把實際變更檔案和 workflow 的 pattern 對照;branches 與 paths 同時存在時,兩組條件都要通過才會觸發。
最容易重現 Pending 的 workflow 寫法
下面的 workflow 只在 scripts/ 變更時觸發:
name: ci
on: pull_request: paths: - 'scripts/**'
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci - run: npm test如果分支保護把 build 列為 required,而 Pull Request 只修改 repository 根目錄的 README.md,這個 workflow 不會建立 run。GitHub 官方的排錯文件也直接以這種情境說明:required check 會留在等待狀態,PR 因此無法合併。
三種修法,依你的檢查邊界選一種
修法一:不把可被 workflow filter 跳過的 check 設為 required
如果 scripts/** 真的只是小範圍檢查,而且其他變更不需要它,可以從 branch protection 或 ruleset 的 required checks 移除 build。這是最直接的修法,但要確認沒有因此漏掉真正需要的安全或建置檢查。
修法二:讓 workflow 每次觸發,把條件放進 job
如果 check 名稱必須穩定存在,讓 pull_request 不用 paths 過濾,再把選擇性條件放到 job。這樣 workflow 至少有機會產生一個可追蹤的 run;被 if 跳過的 job 也和整個 workflow 沒有 run 是不同結果。
name: ci
on: pull_request: merge_group:
jobs: build: if: >- github.event_name == 'merge_group' || contains(github.event.pull_request.changed_files, 'scripts') runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - run: npm ci - run: npm test上面的 changed_files 只作為結構示意,不能直接當作完整的路徑判斷實作。實務上可用 repository 已採用的變更偵測方式產生 output,再把 output 傳給後續 job。重點是:不要直接把 required workflow 在 on 層級整個過濾掉。
修法三:把 required check 做成永遠回報的 gate
若真正的 build job 有多個條件,建立一個最後的 gate job,使用 needs 收集結果,並以 if: ${{ always() }} 確保它會執行。Gate 再根據前置 job 的結果決定成功或失敗,讓分支保護只需要追蹤一個固定名稱。
jobs: lint: runs-on: ubuntu-latest steps: - run: ./scripts/run-lint.sh
build: runs-on: ubuntu-latest steps: - run: ./scripts/run-build.sh
required: if: ${{ always() }} needs: [lint, build] runs-on: ubuntu-latest steps: - name: Fail when a required job failed or was cancelled if: >- needs.lint.result == 'failure' || needs.lint.result == 'cancelled' || needs.build.result == 'failure' || needs.build.result == 'cancelled' run: exit 1 - run: echo 'Required jobs completed'這個範例沒有處理所有專案的變更範圍需求,但示範了 gate 的責任:把「哪些 job 要跑」和「required check 要不要通過」拆開。若前置 job 失敗或被取消,gate 仍會回報失敗,而不是讓 required check 消失。
Merge queue 還要加上 merge_group
如果 repository 啟用了 merge queue,pull_request 和 push 不一定能涵蓋被放進 queue 的測試事件。GitHub 官方要求需要在 workflow 觸發條件中加入 merge_group,否則 required check 可能不會在 queue 的測試合併提交上產生。
最小設定如下:
on: pull_request: merge_group:加上事件後,仍要檢查 workflow 使用的 context。merge_group 的事件資料和 pull_request 不完全相同,不要把所有 github.event.pull_request.* 直接假設成兩種事件都存在。需要路徑判斷時,先為兩種事件分別設計 input,再在測試 PR 和 merge queue 中各跑一次。
提交前的最小驗證表
修改 workflow 或 branch protection 後,至少驗證這四種 PR:
| 測試情境 | 預期結果 |
|---|---|
| 修改會觸發檢查的檔案 | workflow run 建立,required check 完成 |
| 只修改不相關檔案 | 若選擇性 job 被跳過,固定 gate 仍有結果 |
| commit message 含 skip 指令 | 確認這個行為不會和 required check 衝突 |
| PR 加入 merge queue | merge_group workflow 產生對應 check |
如果目前已經卡在 Pending,先把修法提交到新的 commit,再回到 PR 的最新 commit SHA 確認 check。GitHub 文件指出,較早 commit 的成功結果不能代替最新 SHA 上的 required check。
結論:required 的應該是結果,不是偶爾存在的 workflow
GitHub Actions 的 paths 和 branches 很適合減少不必要的執行,但它們放在 workflow 觸發層時,可能讓 required check 完全沒有結果。遇到 Pending,先判斷是 workflow 沒有 run、job 被條件跳過,還是 merge queue 少了 merge_group;再決定要移除 required、把條件下移,或建立永遠回報的 gate。
常見問題
Q: Workflow 被 paths 跳過,為什麼不是 Success?
A: paths 是 workflow-level filter。檔案不符合條件時,整個 workflow 不會建立 run,因此 GitHub 沒有一個可回報的 check 結果。若這個 check 被設定為 required,PR 可能停在 Pending。把條件放進 job,或不要把可被跳過的 workflow 列為 required,才能避免這個邊界。
Q: job 使用 if 被跳過,會不會也卡住?
A: GitHub 文件把條件式跳過的 job 視為 Success,但仍要看它是否被 needs 依賴、是否有另一個 job 需要它的輸出。若 required 檢查由多個 job 組成,使用 always() 的固定 gate 會比直接要求某個選擇性 job 更容易維護。
Q: merge queue 已經有 pull_request,還需要 merge_group 嗎?
A: 需要另外確認。GitHub 把 merge queue 的事件獨立成 merge_group;若 required check 必須在 queue 的測試合併提交上執行,workflow 應明確加入這個 trigger,並測試事件 context 是否符合你的條件判斷。
參考資料:
GitHub Docs:Skipping workflow runs
回報錯字、失效連結,或告訴我你想看的延伸主題。