工作流 21 min read

Claude Code Hook 怎麼用?git push 後自動驗 GitHub Actions 部署(PostToolUse 實戰)

每次 git push 完都要手動切到 GitHub Actions 頁面盯著部署跑完,綠了放心、紅了回來修——這件事重複到讓人煩。我用 Claude Code 的 PostToolUse hook 把它自動化:hook 攔到 git push 成功就注入一段任務指示,叫 Claude 排程約 70 秒後查 Actions 結果,全綠回報、紅了抓 log 修。關鍵是一次性驗證(ScheduleWakeup 而非無限輪詢),而且改版後不綁特定 workflow 名、用 commit SHA 對齊,任何有 push-triggered Actions 的 repo 都通用。本文把實作三段、additionalContext 機制、從寫死 deploy.yml 到通用的兩刀改法,以及踩到的欄位名坑(tool_response 才是對的)全寫下來。

Claude Code Hook 怎麼用?git push 後自動驗 GitHub Actions 部署(PostToolUse 實戰)
本文目錄 · 9
TL;DR
每次 git push 完,我都得手動切到 GitHub Actions 頁面,盯著那顆轉圈的圖示等部署跑完——綠了就放心,紅了才回來修。這件事重複到讓人煩。
後來我用 Claude Code 的 PostToolUse hook 把它自動化:hook 攔到「git push 成功」這個事件,就注入一段任務指示給 Claude,叫它排程約 70 秒後去查 GitHub Actions 的結果,全綠就回報、收工;有 run 失敗就抓 log 修掉。
關鍵是一次性驗證、不是無限輪詢,而且改版後不綁特定 workflow 名,任何有 push-triggered Actions 的 repo 都通用。這篇把實作、機制、跟踩到的欄位名坑全寫下來。

📌 目錄

🧠 為什麼需要這個

我的部落格是 Astro 靜態站,發新文章或改設定都靠 git push 觸發 GitHub Actions 重新 build、部署到 GitHub Pages。整個流程跑完大約一分半。問題不在這一分半本身,而在我得記得回來看它有沒有成功

實際的痛點有三個:

第一,忘記回來看。 push 完我手就去做別的事了,過十分鐘才想起「啊剛剛那個部署成功了嗎」,切回 Actions 頁面才發現紅了一片——但這十分鐘讀者看到的是舊版甚至壞掉的頁面。

第二,盯著轉圈很蠢。 如果乖乖等,那就是人類坐在那裡看一個進度條,什麼產值都沒有。這正是該交給機器做的事。

第三,失敗的當下沒有上下文。 等我十分鐘後想起來、切回去、點進失敗的 run、展開 log,我早就忘記剛剛 push 了什麼、改了哪些檔。重新建立上下文本身就是成本。

我要的東西很單純:push 完,有個東西自動幫我盯,綠了不要吵我,紅了立刻告訴我並且帶著「剛剛改了什麼」的上下文一起修。Claude Code 的 hook 系統剛好能做到。

Claude Code PostToolUse hook git push 自動驗證的單元測試輸出,6 種情境全數通過

🔧 PostToolUse hook 是什麼、能攔什麼

Claude Code 官方 GitHub repo,hook 系統文件來源

Claude Code 的 hook 是一組「在特定事件發生時自動執行的 shell 指令」。它讓你在不改 Claude 本體行為的前提下,於關鍵時間點插入自己的邏輯。常用的三種:

Hook 類型觸發時機典型用途
PreToolUse工具執行參數驗證、攔截危險指令
PostToolUse工具執行成功後自動格式化、跑檢查、注入後續上下文
Stopsession 結束時最終驗證、清理
這個自動驗部署的需求,落點就是 PostToolUse。原因很重要:PostToolUse 只在工具成功時才觸發。也就是說,當這個 hook 被叫到、而且 tool_nameBash、指令包含 git push 時,代表這次 push 本身沒有失敗(沒有 force-push 被拒、沒有 auth 403)。我不需要自己判斷 push 成不成功,hook 被觸發這件事就是證明。

hook 的運作方式是:Claude Code 透過 stdin 餵一段 JSON 給你的腳本,腳本可以選擇性地從 stdout 印一段 JSON 回去,把資訊「注入」回 Claude 的上下文。對 PostToolUse 來說,回注的結構長這樣:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "這段文字會被加進 Claude 的上下文"
  }
}

additionalContext 是整個機制的核心——它讓 hook 不只是「做一件事」,而是能反過來指派任務給 Claude。下一段就是靠它把驗證流程餵回去。

📝 實作:攔 git push 後把任務餵回給 Claude

完整的 hook 腳本放在 ~/.claude/scripts/hooks/post-git-push.sh,註冊在全域 ~/.claude/settings.jsonPostToolUse 區塊、matcher 設為 Bash。我拆成三段講。

第一段:解析 payload、只認真正的 git push

set -euo pipefail
PAYLOAD="$(cat)"
command -v jq >/dev/null 2>&1 || exit 0

TOOL_NAME="$(printf '%s' "$PAYLOAD" | jq -r '.tool_name // empty')"
COMMAND="$(printf '%s' "$PAYLOAD" | jq -r '.tool_input.command // empty')"
CWD="$(printf '%s' "$PAYLOAD" | jq -r '.cwd // empty')"

# 只認 Bash 工具
[ "$TOOL_NAME" = "Bash" ] || exit 0

# command 必須包含 "git push"
case "$COMMAND" in
*"git push"*) : ;;
*) exit 0 ;;
esac

# 排除明顯不是真推送的情境
case "$COMMAND" in
*"--dry-run"*|*"--help"*|*"git push -h"*) exit 0 ;;
esac

重點:matcher 設 Bash,所以每一個 Bash 指令都會進來。腳本得自己過濾,只在指令真的含 git push、而且不是 --dry-run / --help 時才繼續,其餘一律靜默 exit 0。靜默退出不會干擾正常工作流。

第二段:守門——不是每個 repo 都該驗

因為 hook 是全域的,它會在我所有專案的 push 上觸發。但不是每個 repo 都跑 GitHub Actions。所以必須守門:

REPO_DIR="${CWD:-$PWD}"
[ -d "$REPO_DIR" ] || exit 0

REPO_ROOT="$(git -C "$REPO_DIR" rev-parse --show-toplevel 2>/dev/null || true)"
[ -n "$REPO_ROOT" ] || exit 0

WF_DIR="$REPO_ROOT/.github/workflows"
[ -d "$WF_DIR" ] || exit 0

優先用 payload 給的 cwd 定位 repo 根,拿不到才退回腳本當下的 $PWD。如果根本不是 git repo、或沒有 .github/workflows/ 目錄,就靜默退出——不去吵那些不跑 CI 的專案。

第三段:注入驗證任務

過了守門,就用 jq 印出 hookSpecificOutput,把一段詳細的任務指示塞進 additionalContext

read -r -d '' CTX <<'EOF' || true
偵測到 git push 成功,且本 repo 有由 push 觸發的 GitHub Actions workflow。請啟動一次性的 CI/deploy 驗證(不要無限 loop):
  • 先拿剛推上去的 commit SHA:git rev-parse HEAD
  • 用 ScheduleWakeup 排約 70 秒後醒來
  • 醒來後跑 gh run list 篩出 headSha == 那個 SHA 的所有 run
  • - 全部 success → 回報後停 - 任一 failure → gh run view --log-failed 看原因、修、重 push
  • 一次性流程,過了或修完就結束。
  • EOF

    jq -n --arg ctx "$CTX" '{
    hookSpecificOutput: {
    hookEventName: "PostToolUse",
    additionalContext: $ctx
    }
    }'

    這段印出去之後,Claude 在下一輪就會「收到」這段 additionalContext,把它當成接下來要做的事。hook 不自己跑驗證,它只是把驗證任務交棒給 Claude——因為查 Actions、判斷結果、抓 log 修錯這些事需要判斷力,是 Claude 的活,不是一段 shell 該硬幹的。

    ⏰ 一次性驗證:ScheduleWakeup 而不是無限輪詢

    這裡有個很容易做錯的設計決定:怎麼等部署跑完

    直覺做法是寫個 while 迴圈一直 sleep 然後查狀態,跑完才跳出。但這在 agent 環境裡是反模式——它會把整個 session 卡在迴圈裡,而且一旦判斷邏輯出錯就變成無限輪詢,燒 token、佔住對話。

    我用的是 ScheduleWakeup排一個未來的喚醒點,然後把控制權交回去。等到那個時間點,runtime 會帶著我給的 prompt 重新喚醒 Claude,這時才查一次狀態。流程是這樣:

    還在跑/排隊

    全 success

    任一 failure

    git push 成功

    PostToolUse hook 觸發

    是 git push?
    repo 有 workflow?

    靜默 exit 0

    注入 additionalContext

    Claude 拿 commit SHA

    ScheduleWakeup 排 ~70 秒

    醒來: gh run list 篩 headSha

    該 SHA 的 run 狀態?

    回報全綠, 停

    gh run view --log-failed
    修掉, 重 push

    幾個刻意的數字:

    • 70 秒:deploy 約一分半跑完,70 秒先探一次太早也沒關係——還在跑就再排一次。選 70 而不是 90,是因為 Anthropic 的 prompt cache TTL 是 5 分鐘,70 秒讓 cache 維持溫的,下次喚醒讀上下文不必重算。
    • 最多探 3 次(約 3.5 分鐘):超過就不再排,避免 workflow 卡死時無限重排。
    • 全綠才停、紅了才修:成功路徑完全不打擾我;失敗路徑才把我拉回來,而且 Claude 此時還握著「剛剛 push 了什麼」的上下文,修起來不用重建。
    關鍵差別一句話:這是一次性驗證流程,不是 polling daemon。 過了或修完就結束。

    🌐 從寫死 deploy.yml 到通用:headSha 對齊

    第一版我幹了一件蠢事:守門條件寫死成「repo 必須有 .github/workflows/deploy.yml」。在我自己幾個部落格 repo 上它能動,因為剛好都叫 deploy.yml。但這假設很脆。我掃了一下本機的 repo:

    Repoworkflow 檔名第一版 hook 認得嗎
    ai-lecturer-bobdeploy.yml
    forbidden-beautydeploy.yml
    frontenddeploy.yml
    whisperdocker-build.yml✗ 漏掉
    whisper 的 CI 叫 docker-build.yml,第一版 hook 直接視而不見。寫死檔名等於把適用範圍綁死在「剛好這樣命名」的 repo 上。

    改法分兩刀。第一刀:守門不再找特定檔名,改成掃 .github/workflows/ 下有沒有任何「由 push 觸發」的 workflow:

    HAS_PUSH_WF=0
    for f in "$WF_DIR"/*.yml "$WF_DIR"/*.yaml; do
      [ -f "$f" ] || continue
      if grep -qiE '^[[:space:]]*on[[:space:]]*:' "$f" 2>/dev/null \
         && grep -qiE '(^|[[:space:]])push([[:space:]]*:|[[:space:]]|$)' "$f" 2>/dev/null; then
        HAS_PUSH_WF=1
        break
      fi
    done
    [ "$HAS_PUSH_WF" -eq 1 ] || exit 0

    我刻意不解析 YAML——本機沒裝 yq,而用 grep 硬解 YAML 結構容易錯。所以用寬鬆判準:檔案裡同時出現 on: 觸發區塊和 push 字樣就算數。寧可多觸發(驗證本身無害),也不要漏掉。

    第二刀,也是更關鍵的:驗證時不綁特定 workflow 名,改用 commit SHA 對齊。注入的指示叫 Claude 跑:

    gh run list --limit=10 --json status,conclusion,headSha,name,databaseId,event

    然後篩出 headSha 等於剛 push 那個 commit 的所有 run。這樣有三個好處:一個 push 觸發多個 workflow 時,全部都 success 才算過;不管 workflow 叫 deploy.ymlpages.yml 還是 ci.yml 都通用;用 SHA 對齊也避免驗到舊的 run。

    改完跑單元測試,6 個情境全過——其中最關鍵的是 whisperdocker-build.yml 現在也會觸發,證明放寬有效:

    測試情境預期結果
    ai-lecturer-bob 真推送觸發
    whisper docker-build.yml(非 deploy 命名)觸發
    git push --dry-run靜默
    git status靜默
    Edit 工具帶 git push靜默
    HOME 目錄(無 workflow)靜默

    🕳️ 踩坑:三個版本的欄位名都不一樣

    整個實作最坑的不是邏輯,是搞清楚 PostToolUse 從 stdin 收到的 JSON 到底長怎樣。我前後遇到三個互相矛盾的版本:

    來源它說的「工具輸出」欄位對嗎
    某個說明 agenttool_result.text✗ 錯
    我自己憑印象寫進註解tool_output✗ 錯
    官方 docstool_response✓ 對
    最後我直接抓官方文件 code.claude.com/docs/en/hooks 對答案,PostToolUse 從 stdin 收到的真實欄位是:
    {
      "session_id": "abc123",
      "cwd": "/home/user/my-project",
      "hook_event_name": "PostToolUse",
      "tool_name": "Bash",
      "tool_input": { "command": "npm test" },
      "tool_response": "PASS: All tests passed"
    }

    也就是 tool_name / tool_input / tool_response / cwd / hook_event_name

    這件事對我是個提醒:寫進腳本或文件的事實,不要憑印象,去抓最高層級的來源驗一次。 我的 hook 程式碼其實沒用到工具輸出那個欄位(只用了 tool_name / tool_input.command / cwd),所以功能沒受影響,但我註解寫的 tool_output 是錯的——發現後第一件事就是把註解改成正確的 tool_response。錯誤的註解比沒有註解更糟,它會誤導下一個讀程式的人(包括未來的我)。

    順帶一提,回注的 output 結構就沒這麼多版本了,官方一致:頂層 hookSpecificOutput,底下 hookEventName(必填,要等於 "PostToolUse")和 additionalContext

    ❓ 常見問題

    Q1:PostToolUse 跟 PreToolUse 差在哪?我這個需求為什麼用 PostToolUse?
    PreToolUse 在工具執行觸發,可以攔截或改參數;PostToolUse 在工具成功後才觸發。驗部署這件事必須等 push 真的成功才有意義,而且我想利用「PostToolUse 只在成功時觸發」這個性質——hook 被叫到就代表 push 沒失敗,省掉自己判斷成敗的步驟。

    Q2:hook 是全域的,會不會干擾我那些不跑 CI 的專案?
    不會。腳本有守門邏輯:不是 git repo、沒有 .github/workflows/ 目錄、或目錄裡沒有 push-triggered workflow,全部靜默 exit 0,不注入任何東西。只有「真的會跑 Actions」的 repo 才會觸發驗證。

    Q3:為什麼用 ScheduleWakeup 而不是寫迴圈一直等?
    迴圈會把 session 卡住,而且判斷邏輯一出錯就變無限輪詢、燒 token。ScheduleWakeup 是排一個未來喚醒點就把控制權交回去,到點才查一次。它是一次性驗證,不是常駐 daemon——綠了就停、紅了修完就結束。

    Q4:一個 push 觸發了好幾個 workflow 怎麼辦?
    因為驗證是用 commit SHA 對齊(gh run listheadSha),同一個 SHA 觸發的所有 run 都會被撈出來,必須全部 success 才算通過。任何一個失敗就走修復流程。

    Q5:這個做法只能用在 GitHub Pages 部署嗎?
    不是。改版後不綁特定 workflow 名,用 SHA 對齊任何 push 觸發的 GitHub Actions。所以不管 repo 跑的是 Pages 部署、Docker build 還是一般 CI test,只要 workflow 由 push 觸發,這個 hook 都能驗。

    🔗 延伸資源

    author
    陳彥彤

    AI 工程師 · AI 顧問。Java 後端 8 年、AI 工程師 2 年。AI 內訓 · AI 導入顧問 · 前後端與雲端培訓。

    support

    覺得文章有用可以到 GitHub 給個 star,或是透過信箱聊聊 AI 內訓、AI 導入顧問或前後端 / 雲端培訓。

    related

    相關文章

    [AI 工具] · 22min
    ultracode vs ultrathink:不是想更久,是派 16 隻 agent
    ultracode 跟 ultrathink 名字像、做的事完全不同。ultrathink 是 prompt 關鍵字,只讓模型這一回合想更深(最高 31999 thinking token);ultracode 是 /effort 設定,把整個 session 切成 xhigh 推理 + 自動 dynamic workflow 編排,替每個任務派出一隊 agent(最多 16 並行 / 單次上限 1000)。附本機 Claude Code binary 實測字串為證。
    [AI 工具] · 14min
    我把「截圖寫操作手冊」做成 Claude Code skill:screendoc 拆解(4 視角審查 + 一鍵重跑)
    每次系統改版都要重截一遍操作手冊、還老是截圖跟說明對不上?我把這件苦差事做成 Claude Code skill:/screendoc。一句指令偵測前端框架、跑 Playwright 截圖、用 UI/UX + 系統分析 + 新手 + 開發者 4 個視角逐張比對「截圖 vs 說明 vs 原始碼」、不一致就局部重截迭代到全過,最後產一份對外可發表的離線 HTML 手冊。本文拆它在幹嘛、Phase 0–5 跑了什麼、為什麼要 4 視角不是過度設計,以及怎麼透過 yc-plugin 一鍵裝起來用。附我在一個無關的 React + Vite 專案上 round-trip 跑通的實測。
    [AI 工作流] · 19min
    AI 每天早上自動寄 SEO 報告:launchd + GSC API + Firestore + SMTP TLS pipeline(1 小時打造)
    想每天早上打開信箱就看到昨天的 SEO 狀況?跟 Claude Code 講一段話,1 小時後我收到第一封信。完整 pipeline:launchd cron → bash → GSC API + Firestore → Python 組信 → SMTP TLS → Gmail。人類只做 3 件事:建 Gmail App Password、貼一行 launchctl、第一次手動跑驗證。本文公開完整腳本。