D1:撰寫與管理工作流程
D1-O1 設定工作流程觸發條件與事件
工作流程由 on: 決定何時執行。官方把觸發分成排程、手動、webhook 與 repository 事件。寫觸發條件時先問三件事:這個事件在哪個分支的檔案上才會生效?它拿到的 commit 與 ref 是什麼?它跑起來有多大的權限?
只讀預設分支的事件
下列事件只有在工作流程檔位於預設分支時才會觸發:schedule、workflow_dispatch、workflow_run、repository_dispatch、issues、issue_comment、discussion、fork、watch、label、milestone、status、check_run、check_suite、registry_package 等。因此在功能分支修改了排程或手動觸發的檔案,卻發現「按不到」或「不會跑」,第一個要檢查的是檔案是否已經進入預設分支。
不同事件拿到的 GITHUB_SHA 與 GITHUB_REF
| 事件 | GITHUB_SHA | GITHUB_REF |
|---|---|---|
push |
推送到該 ref 的最新 commit | 被更新的 ref |
pull_request |
該 PR 合併 commit | refs/pull/<編號>/merge(開啟且可合併的 PR) |
schedule |
預設分支的最後一個 commit | 預設分支 |
workflow_dispatch |
收到 dispatch 的分支或標籤最後 commit | 收到 dispatch 的分支或標籤 |
workflow_run |
預設分支的最後一個 commit | 預設分支 |
pull_request 測的是「合併後的結果」,不是 PR 分支的尖端 commit。schedule 與 workflow_run 永遠回到預設分支,所以它們不會測到你還沒合併的修改。
排程(schedule)
- 最短間隔是每 5 分鐘一次;預設時區是 UTC,也可以指定 IANA 時區字串。
- 只在預設分支、使用它的最新 commit 執行。
- 高負載時(尤其每個整點)可能延遲,排隊的工作甚至可能被丟棄,所以不要拿它當精準計時器。
- 公開 repository 在 60 天沒有任何活動後,排程工作流程會被停用。
- 遇到夏令時間「跳過的那一小時」時,排程會順延到下一個有效時間(例如 2:30 變成 3:00)。
手動觸發(workflow_dispatch)與 inputs
workflow_dispatch 可以定義 inputs,型別有 string、boolean、choice、number、environment。上限是最多 25 個頂層 input,整個 inputs 資料不超過 65,535 個字元。
inputscontext 會保留 boolean 型別;舊的github.event.inputs會把 boolean 轉成字串,所以if: inputs.dry_run與if: github.event.inputs.dry_run == 'true'並不等價。choice的值是字串;required、default在 input 定義裡設定。
被其他工作流程呼叫(workflow_call)
可重用工作流程必須有 on: workflow_call,在 inputs(型別 boolean、number、string)與 secrets 底下宣告它接受的參數。secret 設成 required: true,呼叫端沒傳就會失敗;但它只檢查有沒有傳,不檢查有沒有值。呼叫端用 with: 傳 inputs、用 secrets: 傳具名 secrets;同一組織或企業內可以用 secrets: inherit 隱含傳遞。Secrets 只會傳給「直接呼叫」的那一層,往下一層要再明確傳遞或再用 inherit。
被呼叫的工作流程拿到的是呼叫端的事件內容,所以它的 GITHUB_SHA 與 GITHUB_REF 就是呼叫端的。
其他常考的觸發細節
pull_request的預設 activity types:沒寫types時,只在opened、synchronize、reopened時執行;pull_request_target的預設相同。pull_request_target:在基底 repository 預設分支的 context 執行,不是合併 commit;官方警告在這個觸發條件下執行不受信任的程式碼可能造成安全弱點,所以需要建置或執行 PR 程式碼時要避開它。- 用
GITHUB_TOKEN觸發的事件通常不會建立新的工作流程執行,這是為了避免遞迴;例外是workflow_dispatch與repository_dispatch。需要「從工作流程再觸發另一個工作流程」時,要用這兩個例外之一,或使用不同的認證。 repository_dispatch:event_type最多 100 個字元,client_payload最多 10 個頂層屬性、65,535 個字元。
範圍與權限
permissions 可設在工作流程層或 job 層。只要明確設定其中任何一項,沒有列出的權限都會變成 none;read-all、write-all 套用到全部,{} 則全部關閉。選事件時要連同它帶來的權限一起想:能用唯讀事件完成的事,不要用有寫入權限的事件。
判斷練習
情境 A: 你在功能分支新增了一個 schedule 工作流程,數小時後都沒有執行。
解析: 排程只在預設分支的檔案上執行。先把檔案合併到預設分支,再等排程時間;順便記住,高負載時間點可能延遲。
情境 B: 一個 workflow_dispatch 的 boolean input dry_run,工作流程裡寫 if: github.event.inputs.dry_run == false,結果永遠不成立。
解析: github.event.inputs 把 boolean 轉成字串,'false' == false 不成立。改用 inputs.dry_run,它保留 boolean 型別。
來源:Events that trigger workflows(GH200-DOC-01)、Workflow syntax(GH200-DOC-10)、Reusing workflow(GH200-DOC-08);2026-11-03 以頁面原文比對本節的數字與條件。Workflow syntax 頁面全文約 23.6 萬字元,我只讀到前 20 萬字元,inputs、permissions、concurrency 的敘述來自已讀部分。
D1-O2 設計與實作工作流程結構
jobs、steps 與條件
- 預設情況下 job 會平行執行;用
needs建立相依。被需要的 job 失敗或被略過時,依賴它的 job 也會被略過,除非它自己的if允許繼續;在 job 的if使用always()就會無論相依 job 結果如何都執行。 jobs.<job_id>.if在套用strategy.matrix之前評估,所以不能用matrixcontext 決定整個 job 要不要建立。steps[*].continue-on-error: true讓該步驟失敗時 job 不失敗。steps[*].timeout-minutes必須是正整數,上限 360 分鐘,不支援小數。runs-on給陣列時,runner 必須同時符合所有標籤。
Contexts 與 expressions
官方列出的 context 有 github、env、vars、job、jobs、steps、runner、secrets、strategy、matrix、needs、inputs。存取不存在的屬性會得到空字串,不是錯誤。
每個位置能用哪些 context 不一樣(節錄):
| 位置 | 可用的 context |
|---|---|
jobs.<job_id>.if |
github、needs、vars、inputs(不含 secrets、env) |
jobs.<job_id>.steps.if |
再加 job、runner、env、steps(不含 secrets) |
steps.run、steps.with、steps.env |
含 secrets、steps、hashFiles |
工作流程層的 env |
github、secrets、inputs、vars |
因此「不能在 if 裡直接比較 secret」不是額外規則,而是上表沒有列出 secrets;常見做法是先把 secret 放進步驟的 env,再在 run 裡判斷。2026-11-03 實測(非官方文件):在 jobs.<id>.if 或 steps.if 使用 secrets,或在 jobs.<id>.if 使用 matrix,整份工作流程在解析時就被拒絕,沒有任何 job 執行,錯誤訊息是 Unrecognized named-value: 'secrets'(或 'matrix');把 secret 放進 job 層級 env(例如 ${{ secrets.NAME != '' }})再用 env 判斷則可行,未設定的 secret 會得到 false。
重要的細節:
vars與secrets:vars是組織、repository、環境層級的設定變數,未設定時是空字串;secrets是可用的 secret,會在日誌中被遮蔽,且 composite action 看不到它(要用 input 傳入)。GITHUB_TOKEN一定在secrets內。github.ref與github.ref_name:前者是完整 ref(refs/heads/<分支>、refs/tags/<標籤>、未合併 PR 是refs/pull/<編號>/merge),後者是短名稱。steps.<id>.outcome與conclusion:outcome是套用continue-on-error之前的結果,conclusion是之後的結果。失敗但設了continue-on-error的步驟,outcome是failure、conclusion是success。jobscontext 只在可重用工作流程使用;jobs.<id>.result可能是success、failure、cancelled、skipped。${{ }}的評估時機:if的條件會在 job 或步驟執行前評估;run內的${{ }}是在 shell 執行前先被替換成文字,這是後面 script injection 的成因,見領域五。
矩陣(matrix)
strategy.matrix 對每一種變數組合產生一個 job(例如 3 個版本 × 2 個作業系統 = 6 個 job,先宣告的變數是外圈)。
include:擴充既有組合或新增組合。每一筆會加進所有「不衝突」的原始組合;如果會覆寫既有值,就變成獨立的新組合。exclude:移除特定組合,可以只寫部分欄位來比對。fail-fast:啟用時,只要一個continue-on-error: false的 job 失敗,所有進行中與排隊的 job 都會被取消。continue-on-error可以由矩陣值決定,例如continue-on-error: ${{ matrix.experimental }},用來讓實驗性組合失敗而不影響整體。max-parallel限制同時執行的矩陣 job 數量,即使還有空的 runner 也不會超過。- 用
runs-on: ${{ matrix.os }}讓每個組合選自己的 runner。 - 矩陣可以由事件資料產生,例如
repository_dispatch的client_payload。
矩陣上限:每次工作流程執行最多 256 個 job(GitHub 代管與自管 runner 都適用,依 Actions 限制頁面;見領域五)。實測(非官方文件):2026-11-03 在公開 repository 實測,沒有設定 fail-fast 時,一個組合失敗後,其他進行中與排隊的組合被取消(預設等同 true);設成 false 則其他組合照常完成。矩陣 job 的顯示名稱是 job-id (值1, 值2),值依矩陣變數宣告順序列出,include 新增的組合也一樣;官方頁面這次沒有讀到這兩點,備考時仍請以官方文件為準。實驗紀錄見 gh-200-actions-lab 的 OBSERVATIONS.md。
服務容器(services)
services: 在 job 內宣告相依服務(資料庫、佇列),每個服務有標籤、image,以及 ports、env、credentials、entrypoint、command 等選項。
| job 的執行位置 | 連線方式 | 要不要設定 ports |
|---|---|---|
在容器內(container:) |
用服務的標籤當主機名稱,例如 redis |
不必 |
| 直接在 runner 上 | 用 localhost 或 127.0.0.1 |
必須對應,例如 6379:6379 |
服務容器需要 Linux:GitHub 代管的 runner 必須用 Ubuntu;自管 runner 要有 Docker 的 Linux 機器。每個 job 會建立全新的容器,job 結束就銷毀。composite action 內不能建立與使用服務容器。健康檢查與 options(例如 --health-cmd)沒有在所讀的官方頁面核對到;2026-11-03 實測,options 帶 --health-cmd 的 redis 服務被接受,日誌出現 health: starting 後變成 healthy,job 才開始執行步驟(非官方文件,備考時請以官方為準)。同一次實測也確認:job 直接在 runner 上且有 ports 時,用 localhost:6379 連得到、用服務標籤 redis 解析不到;沒有 ports 時 localhost 連線被拒;job 在容器內時,用 redis:6379 連得到、localhost:6379 被拒。
YAML 錨點與別名
同一個工作流程檔內,可以用 & 定義錨點、* 引用別名,減少重複(官方範例把 env 區塊與整個 job 做成錨點);展開後與完整寫出等價。官方頁面沒有提到合併鍵(<<)與限制,細節交給 YAML 規格,合併鍵是否支援需自行確認。分析工作流程時要先在腦中把錨點、別名與合併後的結果展開,再判斷實際的設定。
runner 映像與版本固定
ubuntu-latest 目前指向 Ubuntu 24.04 映像,windows-latest 指向 Windows Server 2025;官方說 -latest 是 GitHub 提供的最新穩定映像,可能落後作業系統廠商的最新版本,所以映像更新會讓行為改變。需要穩定的環境就釘住具體版本標籤(例如 ubuntu-24.04)。已預先安裝的軟體清單,在 actions/runner-images 儲存庫中各映像的 README 與釋出說明。Ubuntu 20.04 的汰除、不可變 actions(immutable actions)與版本固定的要求,以及 VS Code 的 GitHub Actions 擴充功能,這次沒有找到可核對的頁面,備考前請自行查證。
判斷練習
情境 A: 矩陣是 os: [ubuntu-latest, windows-latest]、version: [16, 18],你要多加一組 {os: macos-latest, version: 20},同時移除 Windows 的 16。
解析: 用 include 新增獨立組合(macOS 不在原本的 os 裡,所以不與既有組合合併),用 exclude: [{os: windows-latest, version: 16}] 移除指定組合。結果是 4 − 1 + 1 = 4 個 job。
情境 B: 你的 job 在 container: node:20 內執行,並宣告 services: postgres。測試程式用 localhost:5432 連不上。
解析: job 在容器內時,服務的主機名稱是標籤,應連 postgres:5432;只有 job 直接跑在 runner 上時才用 localhost 加 ports 對應。
情境 C: 兩個步驟中,第一個是失敗但設了 continue-on-error: true 的 steps.lint,第二個步驟寫 if: steps.lint.outcome == 'failure'。它會執行嗎?
解析: 會。outcome 是套用 continue-on-error 之前的結果,仍是 failure;若改成 conclusion == 'failure',則不成立(conclusion 是 success)。
來源:Workflow syntax(GH200-DOC-10)、Contexts(GH200-DOC-04)、Running variations of jobs(GH200-DOC-02)、Use Docker service containers(GH200-DOC-05)、Reusing workflow configurations(GH200-DOC-07)、GitHub-hosted runners(GH200-DOC-13);2026-11-03 以頁面原文比對。Contexts 頁面只讀到前 10 萬字元(共約 11.4 萬),needs 與 inputs 的屬性細節沒有讀到。
D1-O3 管理工作流程執行與輸出
快取(caching)
快取鍵(key)的查找順序是:先找與 key 完全相同的項目;找不到時比對 key 的前綴;再依序比對 restore-keys。同一個 restore key 若符合多個快取,使用最近建立的那個。鍵最長 512 個字元,超過會讓動作失敗。
- 不可變:既有的快取無法修改,要更新內容就用新的鍵建立新快取。常用
hashFiles('package-lock.json')讓鍵跟著相依檔案變動。 - 容量與淘汰:每個 repository 預設 10 GB(管理員、組織擁有者或企業擁有者可調高);超過時依最後存取日期從最舊的開始刪除。超過 7 天沒被存取的項目會自動移除。超過 10 GB 的使用量會計費。
- 作用範圍:執行可以還原目前分支與預設分支的快取;PR 執行還可以還原基底分支的快取。子分支、兄弟分支的快取拿不到;不同標籤之間也不共用。PR 執行建立的快取限定在合併 ref(
refs/pull/.../merge),只有該 PR 的重新執行能還原,基底分支與其他 PR 看不到。
成品(artifacts)與保留期
- 預設會把建置日誌與成品保留 90 天;可在 repository、組織與企業層級設定保留期。
upload-artifact的retention-days可以為單一成品設定較短的保留期,但不能超過上層設定的限制。 - v4 的成品是不可變的:同一次執行中不要用同一個名稱覆寫,要為每個階段使用新名稱。沒有指定名稱時預設叫
artifact。 upload-artifact會回傳digest(SHA256);download-artifact重新計算並比對,不符會在介面與日誌顯示警告。download-artifact預設只能下載同一次執行稍早上傳的成品;跨執行下載需要提供 token 與執行編號。指定name下載單一成品,不指定則下載全部,各自放進以成品命名的資料夾。- 要確認到期時間,可用 REST API 回傳的
expires_at;刪除工作流程執行時,也會一併刪除它的成品。GitHub CLI 有gh run delete。以 REST API 刪除成品、日誌與執行的具體路徑,以及允許的保留天數範圍(公開與私有 repository),這次所讀的頁面沒有列出,請在官方 REST 文件確認。
在 job 與 step 之間傳資料
| 想傳什麼 | 做法 |
|---|---|
| 步驟 → 後續步驟的環境變數 | echo "名稱=值" >> "$GITHUB_ENV";寫入的那一步看不到,之後所有步驟看得到 |
| 步驟輸出 | echo "名稱=值" >> "$GITHUB_OUTPUT";步驟必須有 id,用 steps.<id>.outputs.名稱 讀 |
| 擴充 PATH | echo "$HOME/.local/bin" >> "$GITHUB_PATH";正在執行的動作看不到,之後的動作看得到 |
| job → 其他 job | job 的 outputs:,由下游 job 以 needs.<job>.outputs.名稱 讀取;檔案則用成品 |
| 可重用工作流程 → 呼叫端 | 步驟輸出 → job outputs → on.workflow_call.outputs 的 value;呼叫端用 needs.<job>.outputs.名稱 |
可重用工作流程是在 job 層級被呼叫,不是在步驟內,所以它的 GITHUB_ENV 與 env 傳不到呼叫端;矩陣呼叫時,輸出取自「最後一個成功並實際設定了值的執行」。
遮蔽、除錯與註解
::add-mask::值讓日誌把該值(以空白分隔的每個字)換成*;必須在輸出之前註冊,且每個 job 對每個值做一次。::stop-commands::結束權杖暫停命令處理,用::結束權杖::恢復;權杖要隨機且每次執行都不同。::debug::訊息只有在 secretACTIONS_STEP_DEBUG設為true時才顯示;::notice、::warning、::error可以附file、line、endLine、title。
工作摘要(job summary)
寫入 $GITHUB_STEP_SUMMARY 的 Markdown 會成為工作摘要,可放測試結果、覆蓋率、連結。每個步驟的摘要上限是 1 MiB,超過時該步驟的摘要上傳失敗並產生錯誤註解,但不會改變步驟或 job 的狀態;每個 job 最多顯示 20 個步驟摘要。
狀態徽章與環境保護
徽章的網址是 https://github.com/擁有者/儲存庫/actions/workflows/工作流程檔名/badge.svg,可加 ?branch=分支 或 ?event=push;沒有參數時顯示預設分支,該分支沒有執行紀錄時顯示所有分支最近的一次。私有 repository 的徽章無法從外部網站存取。可以在 Actions 頁籤選工作流程,從 ⋯ 選單的 Create status badge 產生。環境保護與審核關卡見領域五。
判斷練習
情境 A: 你的建置快取每次都 miss,鍵是 deps-${{ runner.os }}。有人在相依檔更新後想讓快取跟著更新,卻發現舊快取一直被還原。
解析: 快取不可變,鍵不變就永遠命中舊內容。把 hashFiles 放進鍵(例如 deps-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}),相依檔變動時就會產生新鍵並建立新快取。
情境 B: 步驟 build 用 echo "version=1.2" >> "$GITHUB_ENV" 設定了變數,同一個步驟接著用 $version 卻是空的。
解析: GITHUB_ENV 寫入的值,只有之後的步驟看得到,寫入的當下看不到。要在同一步驟使用,就直接用 shell 變數,或拆成兩個步驟。
情境 C: 同一次執行的 job-a 上傳了名為 report 的成品,job-b 為了「更新」它又用 report 上傳一次。
解析: v4 的成品是不可變的,不能用同名成品當作更新;為每個階段使用新名稱(例如 report-final)。
來源:Dependency caching(GH200-DOC-06)、Workflow commands(GH200-DOC-03)、Store and share data(GH200-DOC-12)、Remove workflow artifacts(GH200-DOC-11)、Add a status badge(GH200-DOC-09)、Reusing workflow(GH200-DOC-08);2026-11-03 以頁面原文比對本節的數字與條件。官方指南所列的「環境保護」本節沒有展開,留到領域五。