月中,2,395 / 3,000
GitHub 每個月給我 3,000 分鐘的 Actions 額度。六月十二號——月中——我的用量是 2,395。
上個月也爆過一次。那次我學乖了,跟 AI 一起訂了一堆節約紀律:文件不開 PR、fixup 攢成一包再推、能省則省。結果呢?這個月還是朝著爆的方向狂奔。節流節了半天,水龍頭根本沒關。
所以這次我換了個問法。我沒有問「怎麼省」,我問我的 AI 助手(Claude Code):
「我的 GHA mins(2,395/3,000)又要爆了,這是正常的嗎?你有解嗎?」
注意我問的是「正常嗎」。這個問法後來證明很關鍵——因為它逼 AI 先去查帳,而不是直接背一段省錢建議給我。
AI 沒給解法,先當會計
它的第一個動作不是回答,是跑了一行指令把當天所有的 workflow runs 數了一遍。然後給我看這張表:
| Workflow | 當天次數 |
|---|---|
| CI | 38 |
| PR Hygiene | 31 |
| Auto-update BEHIND PRs | 29 |
| Deploy | 10 |
一天 109 個 runs。 它估了一下,那一天大概燒掉 400 到 600 分鐘——也就是說,照這個工作型態,我的月額度撐不到一週。
但重點不是數字大,是接下來的兇手分析。
三個兇手,第三個它自己招了
兇手一:O(n²)。 我那天平行開了 6 條 PR。我的 repo 有個好心的自動化:每次有 PR 合進 main,就把所有還開著的 PR 自動更新一輪——每次更新又觸發一次完整 CI。6 條 PR、6 次依序合併,等於每合一條,剩下的全部重跑一次。數學系的朋友已經在點頭了:這是 O(n²)。
兇手二:一個叫 synchronize 的觸發條件。 那個自動更新的 workflow,觸發條件裡多掛了一個 synchronize——意思是「任何 PR 的任何一次 push 都跑我一次」。但一個剛被 push 的 branch 根本不需要被更新(它就是剛更新完才被 push 的)。當天 29 次裡,幾乎全是這種空跑。
兇手三,是 AI 自己。 它在查帳查到一半,突然補了一句:
「另外我今天手動跑了 5 次
update-branch,跟自動更新的 workflow 重複付了一次錢——這是我的浪費,記下了。」
然後它真的把這條寫進了它自己的長期記憶,註明「純浪費,下次不准」。我看著它自首,心情很複雜。一方面帳單有它一份,另一方面——我自己手動操作的時候,可從來沒這麼誠實過。
順便學到一個計費陰招
查帳過程還挖出一個我從來沒注意的細節:GitHub 的計費是按 job 無條件進位到分鐘。
我有個 Secret Scan job,跑 8 秒。收費:1 分鐘。那天它跑了 31 次。我用 31 分鐘的錢,買了大約 4 分鐘的實際運算。
8 秒收 1 分鐘,這個匯率比機場換匯還狠。
「你桌上不就有一台 M2 Pro?」
兇手抓完,AI 列了四個解法,按槓桿排序:改觸發條件、改派工紀律、self-hosted runner、直接吃超額費。
Self-hosted runner。我盯著這個詞看了三秒。
我知道這東西存在。我「知道」很多東西存在。但過去每次額度告急,我的腦迴路都是「怎麼省著用雲端的分鐘」,從來沒有一次轉到「為什麼我要租 GitHub 的 2-core 機器,我桌上這台 M2 Pro 一天 24 小時開著在發呆」。
GitHub 給 private repo 的標準 runner 是 2 核 7GB。我的 Mac 是 M2 Pro、16GB,效能輾過去不說——重點是 self-hosted runner 跑的 job 完全不計費。分鐘額度只算 GitHub 自己的機器。
我以前怎麼沒想到。真的,這篇文章的標題我想了很久,最後發現最誠實的就是這句。
插播一個重要細節:上次陪我省錢的,是上一代模型
寫到這裡必須誠實招認一件事:「我以前怎麼沒想到」的「以前」,其實不是沒人幫我想過——是上一代 AI 跟我一起想的,而我們想出來的是另一種答案。
上個月那次爆量,我的搭檔是 Claude Opus 4.8。它給我的解法是一整套節流紀律:文件改動不開 PR、fixup 攢成一包再推、merge 時機算準一點。每一條都是好建議,每一條也都成立——但你看出共同點了嗎?全部都是「如何在 3,000 分鐘的框框裡活下來」。 我們花了一個月,把自己訓練成一個很會省的房客。
這個月我換上了 Fable 5(Claude 5 家族的新模型),丟給它同一個問題。它查完帳之後給的選單裡,赫然多了一個上次從來沒出現過的選項——「為什麼要租?」
同一個我、幾乎同一句抱怨、同一個 repo。上一代在框框裡幫我優化,這一代直接質疑框框本身。我那套用了一個月的省錢紀律,新模型上線沒幾天就把它變成歷史文件——而且諷刺的是,那套紀律還是寫在它自己的記憶裡的,它讀完之後等於看著前任的筆記說:「嗯,這些都對,但我們可以不用過這種日子。」
公道話也要講:也許有一部分是我這次問了「正常嗎」而不是「怎麼省」。但把「你桌上不就有一台 M2 Pro」這句話說出口的,是新模型。節流是優化,掀桌是解法——這兩種答案的差距,大概就是這次模型升級在我身上最具體的體感。
我只出了一句話:「為什麼不能全部在 Mac 跑?」
先搬了最肥的測試 job 過去之後,我問了 AI 這句話。接下來發生的事,是我覺得這次協作最值得寫的部分。
我心裡想的是「都搬過來啊,有什麼難的」。但 AI 接到這句話之後,沒有直接動手,而是先跑了一輪相容性審計——全是我根本不知道要檢查的東西:
- 掃了所有 workflow 的每一個 action,確認沒有 Docker container action——因為 macOS runner 跑不了 Docker action,搬過去會當場死掉。(我:原來有這種事?)
- 查了我的 Mac 睡眠設定——
pmset顯示sleep 0,系統永不睡眠。runner 掛在一台會睡著的機器上,CI 就會跟著睡著。(我:原來要查這個?) - 確認了我的 CI 沒有任何 Linux 硬依賴——連資料庫遷移都是打遠端 API,不需要本機 Postgres。
這就是我現在的工作分工:我出的是那句「為什麼不能」,它出的是那張我自己根本列不出來的檢查清單。
然後它被自己的剎車擋下來了
這段是我最想講的。
AI 審計完,開始動手裝第二個 runner——然後它的動作被擋下來了。擋它的不是我,是它自己系統裡的安全分類器。拒絕理由翻譯成人話是:
「使用者只是問『為什麼不能全部在 Mac 跑』。問題不等於同意。 在你的 Mac 上裝一個常駐服務、把 production 部署改道到這台機器,需要他明確點頭。」
於是 AI 停下來,老老實實回頭問我:「要全搬嗎?唯一的實質代價是 Mac 掛掉的期間不能部署 hotfix,你都不關機的話我建議全搬,OK 嗎?」我點了頭,它才繼續。
上一篇我寫過「AI 負責怎麼做,但底線得我來劃」。這次反過來了——我興沖沖想直接衝,是 AI 的機制把『等等,先問過他』這條線劃在我前面。 我不知道該驕傲還是該檢討。
兩台 runner,十五分鐘,外加一個防呆陷阱
最後的架構長這樣,全程我沒打一行設定:
m2pro-local(標籤ci):跑測試、lint、secret scan、所有雜活m2pro-deploy(標籤deploy):專門跑部署——獨立一個 runner,部署永遠不用排在測試後面
兩個都是 macOS 的 LaunchAgent 常駐服務,開機自起、自動更新,閒置時各佔 200MB 記憶體、0% CPU。裝完第一個到第二個上線,前後十五分鐘。
中間有個陷阱值得記:runner 會自動帶 self-hosted、macOS、ARM64 這幾個標籤,兩台都有。如果 job 寫 runs-on: [self-hosted, macOS, ARM64],兩台都符合條件——測試 job 就會三不五時搶走部署專用的那台。所以路由必須用自訂標籤 [self-hosted, ci] 和 [self-hosted, deploy] 寫死。這個坑是 AI 在改第一個 job 的時候自己發現、自己在註解裡寫了警告的。
「設定完成」不算數,「真的跑在上面」才算數
我跟 AI 工作久了,有一條雙方都認的紀律:改完設定不等於生效,要拿證據。
所以它合併完之後做的最後一件事,是打 GitHub 的 API,把每一個 job 實際分配到的 runner 名字撈出來:
changes → m2pro-local (success)
Lint & Test → m2pro-local (success)
Secret Scan → m2pro-local (success)
Apply Supabase migrations → m2pro-deploy (success)
Deploy Workers → m2pro-deploy (success)
Deploy Next.js Apps → m2pro-deploy (success)
Health Check → m2pro-deploy (success)
每一行都是「真的跑在我 Mac 上」的收據,不是「設定檔改好了」的自我感覺。
最後它還順手寫了一個 hook:每次我開新的工作 session,自動檢查兩台 runner 活著沒——因為 self-hosted 有個陰險的故障模式:Mac 重開機後沒登入,runner 不會起來,所有 CI 不會失敗、只會靜靜地排隊,看起來就像 GitHub 壞了。與其哪天我對著 pending 的 CI 抓頭半小時,不如讓系統開工時就喊一聲。
(看過我前兩篇的朋友應該發現了:又是 hook。我家現在的紀律全都長這樣——不是「我下次會記得」,是「系統會幫我記得」。)
所以到底省了什麼
老實的比較表:
| GitHub hosted | 我的 Mac | |
|---|---|---|
| 機器 | 2 核 / 7GB | M2 Pro / 16GB |
| 最肥的測試 job | 3–6 分鐘 | 3 分半(冷快取)起,之後更快 |
| 計費 | 按 job 進位,8 秒收 1 分鐘 | 0 |
| 月額度焦慮 | 月中 2,395/3,000 | 不存在這個概念 |
| 代價 | 刷卡 | Mac 要開著(本來就開著) |
純粹算錢的話,其實沒省多少——就算直接吃超額費,一分鐘 $0.008,一個月頂多十幾二十美金。真正省下的是另外兩樣:
一是速度不再受限於那台 2 核小機器。二是——這個才是大的——我的工作方式不再被額度扭曲。之前為了省分鐘,我們把 fixup 攢著不推、文件繞過 PR、merge 排程算來算去,整套流程都在配合一個會員方案的數字。現在那個數字消失了,CI 想跑幾次跑幾次。
省錢事小,省掉「省錢行為」事大。
四個帶走的
1. 「這正常嗎?」是比「怎麼辦?」更好的開場。 問怎麼辦,AI 會給你通用建議清單;問正不正常,它會先去查帳。109 個 runs 那張表一出來,解法自己會浮現——而且連 AI 自己那 5 次浪費都一起浮出來了。
2. 「為什麼不能 X?」是被低估的 prompt。 我那句「為什麼不能全部在 Mac 跑」,換來的不是「可以/不可以」,而是一張 Docker 相容性、睡眠設定、Linux 依賴的完整審計清單。把「我想做 X」說成「為什麼不能 X」,AI 會把所有擋路的東西翻出來給你看——而那張清單本身,就是遷移計畫。
3. 紅線不一定都是人劃的。 上一篇是我劃線、AI 動手。這次 AI 的安全機制反過來對我劃線:「問題不等於同意。」一開始被擋我有點不耐煩,後來想想——一個會在「把你的 production 部署改道到你家桌機」之前堅持要你親口說好的系統,正是你敢放手讓它動手的原因。
4. 模型升級的體感,不是答得更快,是跳得出框。 同一個問題,上一代給我節流紀律,這一代給我「為什麼要租」。如果你也長期跟 AI 搭檔工作,模型升級後別只拿舊問題驗證它答得好不好——把那些你已經「解決過」的問題重新丟給它一次。 你以為早就結案的事,新模型可能會告訴你:你當初只是學會了忍。
(閉環小註:上上篇它幫我蓋了 worktree 的房間,上一篇它幫我清了沒退房的殭屍,這一篇它直接把 GitHub 的機房搬進了我家。照這個趨勢,下一篇大概是它幫我付電費。)