---
name: gh-ci-watch
description: "Use when 需要監看或查詢 GitHub Actions（push 後盯 CI / deploy 綠燈、等某 run 或某 SHA 完成、撈 run log 證據、查 runner 佇列）。NOT for 修 CI 紅燈本身（那是拿到結果後的除錯流程）。"
metadata:
  author: clade
  version: "1.0"
permission_tier: read-only
---
<!-- 🔒 LOCKED — managed by clade · auto-generated by sync-to-codex; edit source in .claude/ then re-run sync -->

# /gh-ci-watch — GitHub Actions 監看 / 查詢唯一入口

**核心 contract**：監看 CI = **機械輪詢**。用 `Bash(run_in_background=true)` 跑 `gh-ci-watch.sh`——腳本自己 poll 到 terminal state 才 exit，主線只在**完成時收到一次通知**。等待期間零 LLM turn、零 token、行為 100% 確定。

Script 位置：

- Consumer 端（hub-core 投影）：`.codex/scripts/gh-ci-watch.sh`
- Clade home：`plugins/hub-core/scripts/gh-ci-watch.sh`

## 機制選擇：為什麼是 background Bash + script，不是其他

> 這段是本 skill 存在的理由。**NEVER** 退回用 Agent subagent 監看 CI——那正是本 skill 要根治的事故根因。

**事故實證（2026-07-25，perno v0.99.7 發版）**：依當時規約派兩個 `Agent(run_in_background=true)` watcher subagent 監看 Deploy Staging / Production，實際發生四件事：(1) brief 明寫「completed 才回報」，agent 仍反覆中途回報「持續監看中…」，每次回報都是一次 LLM turn，累計 **235k+ tokens 且沒有產出最終結果**；(2) 監看的 staging run 被 concurrency `cancel-in-progress` 取消後，agent 繼續空等已死的 run；(3) SendMessage 改派新 run id，agent 口頭答應卻仍回報舊 run 結論；(4) 同一份 brief、同一個 model，兩個 watcher 行為不一致。

| 機制 | 判定 | 理由 |
| --- | --- | --- |
| **`Bash(run_in_background=true)` + 本 script** | ✅ **採用** | 官方定位就是「單次通知：告訴我 X 好了沒」。腳本達 terminal state 即 exit → 剛好一次通知；無 LLM 參與 → 零等待成本、行為確定。事故中需要「判斷力」的三件事（run 尚未建立、被 concurrency 取代、同 SHA 多條 run）其實都是**機械規則**，已全部編進 script（Phase 1 pending 重查、Phase 2 successor 追蹤、`--since`/`--commit` 過濾），不需要 LLM |
| `Agent(run_in_background=true)` | ❌ 禁用 | 見上方事故四點。LLM「判斷力」在這個場景是負資產：不可預測 + 每個動作燒 token。唯一例外見下方「例外」節 |
| `Monitor` | ❌ 不用 | 官方定位是「**每次發生**都要通知」的事件流（`tail -f` 型）。CI 監看要的是**恰好一次**完成通知——用 Monitor 做單次通知違反其官方告誡；且 filter 若沒涵蓋所有 terminal state，crash 時 monitor 沉默、沉默看起來跟「還在跑」一模一樣（官方「沉默不等於成功」告誡）。本 script 用 `RESULT:` 行涵蓋全部 terminal state，從結構上排除這個坑 |
| 主線 `ScheduleWakeup` / 前景 `gh run watch` | ❌ 不用 | 佔用主線 context / block 主線對話。`gh run watch` 也不處理 run 被取代 |

## 監看：canonical dispatch 樣板

以下命令一律用 **`Bash(run_in_background=true)`** 派出（cwd = 該 repo，或帶 `--repo <owner>/<repo>`），派出後主線**繼續原本工作**，等系統的完成通知。

### 場景 A — 盯已知 run id

```bash
bash .codex/scripts/gh-ci-watch.sh run <run-id>
```

### 場景 B — 盯某 workflow 最新一條 run（push 後標準場景）

```bash
bash .codex/scripts/gh-ci-watch.sh workflow "Deploy Staging" --branch main
```

- **run 尚未建立也可以直接派**：`/commit` 是 `git push --tags` 先、`git push main` 後，staging run 可能還不存在——script 把「查無 run」視為 pending 繼續等（預設只認腳本啟動前 120s 之後建立的 run，可用 `--since <ISO8601>` 調整）
- run 被 concurrency `cancel-in-progress` 取代 → script 自動改追 superseding run（同 workflow + 同 branch、createdAt 較新者）
- **tag 觸發的 workflow MUST 用 `--commit "$(git rev-parse HEAD)"`，NEVER 用 `--branch main`**：tag 觸發的 run 其 `headBranch` 是 **tag 名**不是 `main`，`--branch main` 對它永遠篩不到 run → watcher 一路 pending 到 `WATCH_TIMEOUT` exit 3，即使該 run 其實是綠的（2026-07-25 TDMS v1.250.0 實證）。`--commit` 對 tag 與 branch 兩種觸發都成立，post-push 場景一律用它；`--branch v1.2.3` 只在明確要盯單一 tag 時用

### 場景 C — 等某 SHA 的某 workflow 出結果

```bash
bash .codex/scripts/gh-ci-watch.sh workflow "Deploy Production" --commit "$(git rev-parse HEAD)"
```

**`--commit` MUST 給完整 SHA**（`$(git rev-parse HEAD)`，別從 `git log` 抄 7–8 碼縮寫）。`gh run list -c` 只認 40 碼，縮寫會**靜默回空陣列**、不報錯；script 自 2026-07-31 起會先用 `git rev-parse` 展開，展不開就 fail fast 回 `UNAVAILABLE`（先前是誤判成「run 尚未建立」等滿 3600s）。

同 SHA 多條 run（rerun 過 / concurrency 產生）時取 createdAt 最新一條；失敗照實回報 `RESULT: failure`（**不**默默等 rerun——failure 的處置是主線的事）。

### 場景 D — 完成後順帶抓證據行

```bash
bash .codex/scripts/gh-ci-watch.sh workflow "Deploy Staging" --branch main \
  --evidence-grep 'Deploy complete|digest: sha256'
```

Terminal report 一律自帶：`RESULT:` 行、run URL、各 job 耗時（`--json jobs` 計算）、失敗時 `--log-failed` 前 200 行；`--evidence-grep` 額外對 full log 撈前 40 行命中。pattern 要**收斂**（具體字串），別用 `image|build` 這種寬 pattern 撈一堆雜訊。

### 常用 flags

| Flag | 預設 | 說明 |
| --- | --- | --- |
| `--interval <sec>` | 30 | 輪詢間隔；**<30 會被 clamp 回 30**（GitHub API 紀律） |
| `--timeout <sec>` | 3600 | watch 上限。單槽 self-hosted runner queued 30+ 分鐘是常態，**NEVER** 因為「應該很快」調低到 <1800 |
| `--no-follow` | 追 | cancelled 時不追 superseding run（罕用；例如刻意驗證 cancel 行為） |

### Exit codes / RESULT 分流

輸出尾段**保證**含 `RESULT: <state>` 行——涵蓋所有 terminal state，**沉默不可能等同成功**：

| exit | RESULT | 主線處置 |
| --- | --- | --- |
| 0 | `success` | 一行回報綠燈 + run URL，結束話題 |
| 1 | `failure` / `cancelled`（無 successor）/ `timed_out` / `startup_failure` / ... | 讀同段輸出的 `--log-failed` 節錄，進失敗處置流程（post-push 場景見 AGENTS.md「Post-Push CI Watcher」段的 request_user_input 二選一） |
| 2 | `UNAVAILABLE (<原因>)` | gh 不存在 / 未登入 / API 連續失敗——一行回報略過，**NEVER** 追問 user |
| 3 | `WATCH_TIMEOUT` | run 可能仍在跑（輸出含最後已知狀態 + run id）。可再派一輪 `run <run-id>` 續盯，或依場景處置 |

## 查詢：canonical 命令（一次性，前景跑即可）

查詢不是監看——一次 `gh` call 拿得到答案的，直接前景 Bash 跑，不派 background。

```bash
# 列最近 run（含狀態）
gh run list -L 10 --json databaseId,workflowName,status,conclusion,headBranch,createdAt,url

# 看單一 run 概要 / jobs
gh run view <run-id> --json status,conclusion,jobs,url

# 撈特定 log 行當證據
gh run view <run-id> --log | grep -E '<pattern>' | head -40

# 失敗 log 節錄
gh run view <run-id> --log-failed | head -200

# 查 runner 佇列（單槽 self-hosted runner 排隊診斷）
gh api "/repos/<owner>/<repo>/actions/runs?status=queued" --jq '.workflow_runs[] | [.id, .name, .head_branch, .created_at] | @tsv'
```

## 收到完成通知後主線必做

1. 讀該 background bash 的輸出**尾段**（`=== CI WATCH RESULT ===` 起），依 `RESULT:` 分流（上表）
2. **NEVER** 沉默等 user 問進度——通知到了就主動回報
3. 要**改監看目標**（例如發現該盯另一條 run）：**kill 舊 background bash、派新命令**。**NEVER** 嘗試對跑一半的 watcher「下改派指令」——那是 Agent watcher 時代的失敗模式，script 沒有也不需要互動管道

## NEVER

- **NEVER** 用 `Agent(run_in_background=true)` 開 watcher subagent 監看 CI（本 skill 的存在理由；例外見下）
- **NEVER** 用 `Monitor` 做「完成了告訴我」的單次通知
- **NEVER** 前景 `gh run watch` / 主線 `sleep` 輪詢 block 對話
- **NEVER** 手寫 ad-hoc `until ...; do sleep ...; done` 輪詢取代本 script——ad-hoc loop 幾乎必漏 terminal state 覆蓋（cancelled / 取代追蹤 / UNAVAILABLE），那些坑 script 都處理了
- **NEVER** 輪詢間隔 <30s（script 已 clamp，手寫查詢 loop 也適用同紀律）
- **NEVER** 把「沒收到通知」解讀成任何結論——去讀 background bash 狀態確認它還活著
- **NEVER** 同一條 run 重複派第二個 watcher（改目標 = kill + 重派）

## 例外：什麼時候仍可用 Agent

只有當「完成後的**處置**」需要多步 LLM 工作且 user 明確要求全自動接手時（例如「紅燈就自己修到綠」），才包一層 Agent——而且該 Agent 內部**仍 MUST** 用本 script 等待，等待本身永遠不交給 LLM。純監看 + 回報（絕大多數場景）一律直接 background Bash。

## Cross-ref

| 主題 | 位置 |
| --- | --- |
| Push 後何時觸發監看、綠燈/紅燈後主線的處置政策（request_user_input / HANDOFF 登記） | consumer AGENTS.md 注入段「Post-Push CI Watcher」（source: `claude-md/core-snippets/post-push-ci-watch.md`） |
| Script 本體 | `plugins/hub-core/scripts/gh-ci-watch.sh`（投影至 consumer `.codex/scripts/`） |
| 背景派工通用回報契約 | `rules/core/agent-routing.md` § Subagent 回報契約 |
