❯❯守門人也會犯錯!Playwright Pre-push 閘門、PR CI 快速防線與兩次自動化事故復盤
📍 流水線位置|入口 → flow → 型別 → mock → 測試 → UI → vibe → 【上線】(守門)

Day 20 的本地閘門(gate)作用於 git commit 階段,能在開發第一時間攔下各種意料之外的破壞。但 commit 說到底只是本機變更的起點,程式碼進入生產環境前,真正的防線始終是 main 分支,因為只要一合進 main 的變更,就會直接觸發自動部署管線。
也因此,在變更正式合進 main 分支前,系統設計了兩道不同職責的檢查關卡。
執行 git push 時,pre-push hook 會自動跑系統檢查,它沿用 Day 20 的同一套設定檔,一口氣跑完 main spec 與 vibe spec 的自動化測試。只要環境支援 Docker,它甚至會拉起環境的映像檔進行隔離測試,它測的是真正打包出來的 production build,而不是拿 dev server 跑跑看就算過關。
既然commit 測過了,push 為什麼還要再測一次?因為大家開發時隨手 commit 是常態,中途改壞了也很容易沒發現;但 push 一送出去就是遠端的事了,影響範圍完全不同,本來就該用最嚴格的標準把關。這一關說穿了就是幫大家的人為疏失墊底:攔下那些「忘記跑測試就推 code」以及藏在邊邊角角的錯誤。
跑完整測試要花 5 至 6 分鐘。等待固然煩人,但比起事後去清洗被污染的 main 分支,這個時間成本划算太多了。
為了避免無謂的等待,hook 內建了變更範圍判斷機制(節錄自 .husky/pre-push 的註解):
# 最佳化:若本次 push 的 commit「只動到非程式碼檔」(.env*/docs/docker 設定等),
# 這些變更不會影響被測程式邏輯,因此跳過測試,不必空等。
# 保守原則:只要有任何一個程式碼檔被改到,就照常跑全套測試。
只要這次推送只涉及文件或設定檔,系統會立刻放行,把阻力降到最低,防止有人因為心煩而動念繞過 CI 檢查。
當 Pull Request 建立後,CI 會在完全乾淨的雲端環境裡跑校驗流程(節錄自 pull_request.yml):
- run: npm ci
# 快速守門先行(數十秒內),紅燈即擋 PR,不用等 build
- run: npm run test:unit
- run: npm run build
- run: npm run eslint # ← 含 Day 20 那支自製的視覺層級檢查
留意這裡的順序巧思:單元測試(unit test)被特意擺在建置(build)前面。只要測試沒過,幾十秒內就會直接掛紅燈並中斷 PR,完全不用白白浪費時間等 Build 跑完。
只有這幾關全部 pass,才拿得到合進 main 的通行證。由於端對端測試(E2E)執行成本高,所以我安排在本地 pre-push 階段解決,雲端 CI 則專注在「乾淨環境」的相容性校驗。
既然本地都跑過 pre-push 了,幹嘛還要在雲端多此一舉?
答案很簡單:pre-push 終究是在你家電腦跑的,隨時可能被本地 node_modules、殘留環境變數或沒進版控的檔案給「開美顏」;而 CI 則是跑在零污染的雲端,驗證的是「程式碼一旦離開你的電腦,到底還建不建得起來、跑不跑得動」。

兩者職責切割明確:
pre-push防範本機的人為疏失,CI 則防止本機環境的自信偏見。
這套守門流程其實也在實際運作中踩過兩次大坑。
第一次事故(Day 15):CI 漏勾測試項目。
當時 CI 設定檔中漏寫了 test:unit 項目,果即使有 5 個單元測試處於壞掉狀態,CI 卻依然一路綠燈把程式碼合進 main。檢查機制本身運作正常,但壞在我忘了把相應的測試腳本納入檢查範圍。事後才將單元測試補回 CI 的必要路徑,確立了「快速守門先行」的順序。
第二次事故:SSH 遠端連線 Time Out。
原因出在 git push 與 hook 執行的時間差。系統會先與 GitHub 建立 SSH 連線,連線建立後才執行本地的 pre-push hook;由於測試流程需耗時 5 至 6 分鐘,這段時間 SSH 連線完全沒有資料傳輸,結果直接觸發 GitHub 伺服器的逾時機制,被強制斷線。

這也是最坑的地方:Terminal 亮綠燈說測試通過,但 git push 其實根本沒成功。回去翻 log 才知道 GitHub 早就斷線了( Connection to github.com closed by remote host)。測試過關卻沒 push 上去,這種沒有警告的失敗,殺傷力比直接噴錯卡住大太多了。
修復分成兩個層次:
1. 治本(維持連線):
在本地 ~/.ssh/config 的 github.com 加上 ServerAliveInterval 60 設定,確保在執行測試時每 60 秒持續送心跳(Heartbeat),不讓 GitHub 把連線當死連結切掉。
2. 驗收(雙重確認):
推送完成後,執行 git ls-remote 對比遠端與本地 HEAD 的 commit sha,確保變更真的有落到遠端伺服器上。
最後也在 pre-push 腳本補上了錯誤處置與修復指引:
→ gate 已全綠但 push 因 SSH timeout 中斷時,可 --no-verify 重推(驗證已完成非跳過)。
→ 其他情況勿用 --no-verify(CLAUDE.md 政策禁止)。
明確定義了跳過檢查(--no-verify)的唯一例外條件:僅限於測試已於本地完整驗證通過,卻卡在傳輸層斷線的情境,其餘情況仍嚴格禁止繞過檢查。
這兩次踩坑也提醒了我:自動化防線本身,也是需要被測試與驗證的程式碼。
守門機制徹底搞定後,下一個課題是:通過檢查的變更怎麼自動交付到生產環境,以及部署流程中藏著哪些架構風險?
🎒 最小一步|打開你的 CI pipeline 看執行順序:耗時最長的步驟是不是排在最前面?試著把數十秒就能出結果的檢查(單元測試、Linter)搬到最前面,壞掉時就不用多等幾分鐘白工。
📎 本篇證據|.husky/pre-push(skip 白名單註解與紅燈分流指引原文)・.github/workflows/pull_request.yml(「快速守門先行」註解原文)・issue #76(test:unit 補進 CI)・pre-push SSH 假成功事故(keepalive+ls-remote 修法,留在 pre-push#L70-L71 的錯誤處置指引)