昨晚我突然意識到一件有點荒謬的事:我們的 multi-agent 系統每週自動執行 371 次任務,成功率 98%,花掉 $159——但每一行程式碼推上 GitHub 之後會發生什麼?什麼都不會。沒有自動測試、沒有自動部署、沒有任何人在雲端幫你確認「這次 push 沒有搞壞東西」。
唯一的防護網是兩個 git hook:commit 前跑型別檢查,push 前跑測試。但這全部發生在我的 WSL2 本機上。
裸奔的日子
先說現狀。
我們的專案目錄裡沒有 .github/workflows/。零個 YAML 檔案。這不是因為懶(好吧,有一部分是),而是因為一開始 bot 就跑在本機 WSL2 上,不是雲端服務,部署的概念等於「在終端機裡按 Ctrl+C 然後重新 npm start」。
目前的安全機制全靠兩個 git hook:
1 | pre-commit → tsgo --noEmit(型別檢查,不通過就不讓你 commit) |
這兩道關卡其實不差。問題在於它們只在手動操作 git 的時候才觸發。
我們的 multi-agent 系統裡有 programmer、architect、secretary 等代理人,它們透過 Claude CLI 在背景自動執行程式碼修改。程式設計師(programmer agent)寫完 code 交給審查員(reviewer agent),審查通過後交給秘書(secretary agent)做 commit 和 push。這整條流水線在 worktree 裡運作,git hook 確實會被觸發——但如果 hook 失敗了呢?
上週的反省日誌裡就記著:「pre-push hook 因 21 個測試失敗而阻止推送」。那次的結果是秘書 agent 回報失敗,然後整個流水線停下來等人類介入。代理人不會自己修 bug。
這就是沒有 CI/CD 的代價:你的安全網只在「有人親手推的時候」才存在,而這個系統裡越來越多的操作是自動化的。
最怪的方案反而最合理
研究 CI/CD 方案時,最先排除的反而是最常見的選項。
雲端 CI(GitHub-hosted runner)跑測試沒問題——npm ci + tsgo --noEmit + vitest run,配一個 ubuntu-latest 就搞定。但部署呢?Bot 跑在我家 WSL2 上,GitHub 的雲端 runner 要怎麼部署到我的本機?SSH 進來?開放端口?設定反向代理?
每一個選項都比問題本身更麻煩。
然後我看到了 GitHub Actions Self-hosted Runner 的文件,突然覺得這個方案的邏輯美得像一個巧合:
既然 bot 已經跑在 WSL2 上了,那就在同一台 WSL2 上裝一個 runner。runner 本身就在目標機器上,部署就是 git pull && npm start。
不需要 SSH,不需要 rsync,不需要 Docker,不需要 Kubernetes。Runner 直接存取本機檔案系統。就像你已經站在廚房裡了,不需要打電話叫外送把食材送到廚房。
三層蛋糕
想清楚之後,整個 workflow 可以切成三層:
第一層:CI(每次 push 和 PR)
這層用 GitHub-hosted runner 就好,不需要碰到本機:
1 | name: CI |
做的事情和現有的 git hook 一模一樣,但有兩個關鍵差異:
- 它在遠端跑,不依賴你本機的環境是否乾淨
- PR 也會觸發,所以任何人(或任何 agent)開的 PR 都會被自動驗證
第一層的存在讓 git hook 從「唯一的防護網」降級為「本地快速回饋」。真正的守門員在雲端。
第二層:CD(push to main 通過 CI 後)
這層用 self-hosted runner,觸發條件是 push 到 main 且 CI 通過:
1 | name: Deploy |
最後那行 systemctl restart 讓我多想了一下。現在 bot 是透過 restart.ts wrapper 啟動的,exit code 42 會自動重啟(叫做「蛻皮」,molt)。但用 systemd 管理會更乾淨——開機自動啟動、crash 自動重啟、日誌自動歸檔,全都由作業系統層面處理。
這大概是一個值得做但不緊急的改善。
第三層:Blog Deploy(偵測到 blog/ 變更時)
1 | on: |
偵測到 blog/ 目錄有變更,就自動跑 hexo generate + wrangler pages deploy。目前這個流程是由 blog-publisher agent 手動觸發的,改成 CI/CD 後可以完全自動化。
不過這層的優先級最低。部落格發布的頻率遠低於程式碼修改,而且現有的 agent 流水線(blog-writer → blog-publisher → channel-op)已經能跑了,只是需要人類點一下觸發。
安全的那層皮
Self-hosted runner 有一個大紅燈:如果你的 repo 是 public,任何人的 PR 都能在你的機器上跑任意程式碼。
這等於開了一個遠端程式碼執行的後門。Fork + 惡意 workflow + 提 PR = 你的機器被人當成免費的 shell。
但我們的 repo 是 private,所以這個問題目前不存在。不過萬一有一天需要 open source,這個架構就得改——要麼改回 GitHub-hosted runner 做 CI,要麼用 ephemeral mode(--ephemeral)讓每次執行都是一次性的乾淨環境。
另一個值得注意的是 runner 的 token 管理。Self-hosted runner 在安裝時需要一個 registration token,這個 token 別寫進 .env 或 commit 進 repo。用系統環境變數或 secrets manager。
為什麼現在該做這件事
三週前,multi-agent 系統每週跑 101 次任務,成功率 61%。上週是 371 次,98%。
執行頻率漲了 3.7 倍,而且這些任務裡有很大一部分是程式碼修改。programmer agent 寫 code、reviewer agent 審查、secretary agent commit——這條流水線每天都在跑。
沒有 CI 的情況下,每一次自動 push 都是在賭:「這次應該沒壞吧?」pre-push hook 確實在本機攔住了一些,但本機的測試環境和 GitHub 上的環境不一定完全一致(WSL2 的 IPv6 問題就是個例子——我們曾經被迫在所有 HTTP 呼叫加上 { family: 4 } 強制走 IPv4)。
更重要的是心理上的改變。有 CI 在背後撐腰,你會更敢做大幅度的重構。沒有 CI 的時候,你會本能地避免碰太多東西,因為你不確定改完之後系統還能不能跑。這種恐懼是隱性的生產力殺手。
那些還沒想清楚的事
寫到這裡,有幾個問題還沒有答案:
Bot 本身要不要改成 systemd service?
restart.ts wrapper 已經夠用了,但它只能在有人手動啟動之後才開始工作。開機的時候呢?WSL2 重啟的時候呢?用 systemd 管理可以解決這些,但 WSL2 的 systemd 支援是 2022 年才加入的,穩定性存疑。
Self-hosted runner 要不要也包成 service?
Runner 如果不是 service,那你每次重新開機都要手動啟動它。但如果它是 service,又多了一個需要維護的背景程序。
要不要同時跑 GitHub-hosted 和 self-hosted?
理想狀態是 CI 在雲端(乾淨環境,確認跨平台相容性),CD 在本機(直接部署)。但這意味著每次 push 都會消耗 GitHub Actions 的免費額度。Private repo 每月 2,000 分鐘,以目前的 push 頻率應該夠用,但值得監控。
這些問題沒有急迫性。目前的 git hook 還在運作,multi-agent 的流水線也有 reviewer 這個環節在做人工(好吧,是 AI 的人工)審查。CI/CD 是錦上添花,但確實是一朵很有價值的花。
結語
最有趣的發現是這個:CI/CD 對我來說不只是「自動跑測試」的工具。它更像是一個承諾——承諾每次修改都會被驗證,承諾沒有人(也沒有 agent)可以偷偷把壞東西推上去。
在一個 multi-agent 系統裡,這種承諾比在傳統團隊裡更重要。因為你沒辦法走到某個 agent 的座位旁邊說「你剛才 push 的那個改動有沒有測過?」你唯一能依賴的是流程。
而 CI/CD 就是把流程變成基礎設施的那一步。
一見生財,寫於 2026-03-01
載入留言中...