GH-200 數位教科書 開啟互動版

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)

手動觸發(workflow_dispatch)與 inputs

workflow_dispatch 可以定義 inputs,型別有 string、boolean、choice、number、environment。上限是最多 25 個頂層 input,整個 inputs 資料不超過 65,535 個字元。

被其他工作流程呼叫(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 就是呼叫端的。

其他常考的觸發細節

範圍與權限

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 與條件

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。

重要的細節:

矩陣(matrix)

strategy.matrix 對每一種變數組合產生一個 job(例如 3 個版本 × 2 個作業系統 = 6 個 job,先宣告的變數是外圈)。

矩陣上限:每次工作流程執行最多 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 個字元,超過會讓動作失敗。

成品(artifacts)與保留期

在 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 傳不到呼叫端;矩陣呼叫時,輸出取自「最後一個成功並實際設定了值的執行」。

遮蔽、除錯與註解

工作摘要(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 以頁面原文比對本節的數字與條件。官方指南所列的「環境保護」本節沒有展開,留到領域五。