2026-04-30,我把這個 blog 的 repo 翻成 private,順手推了一版部署。檔案上傳成功,然後 CI 死在最後一步:

[ERROR] A request to the Cloudflare API
  (/accounts/***/pages/projects/terryyaowork/deployments) failed.
  Invalid commit message, it must be a valid UTF-8 string. [code: 8000111]

Success! Uploaded 3 files 就印在上面兩行。檔案都到了,只是註冊 deployment metadata 那步不肯收我的 commit message——一則中文的 commit message。

我用 xxd 檢查過 bytes,是合法的 UTF-8。git 完全沒意見。是 Cloudflare 那端的驗證比 git 嚴格,大概對全形標點( )或某些 codepoint 有意見。更煩的是它不穩定:前面幾個中文 commit 有些推得上去,有些不行。

我沒有查下去,而且我到現在還覺得這是對的

我沒有去 reverse engineer 到底是哪個 codepoint 觸發的。我做的事情是:

git commit --amend -m "infra: prep for private repo + private knowledge base"
git push blog v4 --force-with-lease

換成純英文,CI 立刻過。整件事十分鐘結束。

這個決定我不打算道歉——別人家服務的 bug,不是你的除錯範圍。你查到確切觸發條件也不能修它,最好的下場是拿去開一張 issue 等三個月。繞過去是對的。

但這裡有個副作用值得單獨講,因為它比原本那個 bug 更痛。

force push 那次差點吃掉別人的 commit

我 amend 的那幾分鐘裡,另一個 process 往同一個 branch 推了一筆 commit(df30278,五篇草稿)。我的 --force-with-lease 推上去之後,那筆的 SHA 被改寫了。

內容沒丟,被併進我的新 commit 裡,但它原本的 commit message 消失了——那筆的敘述再也找不回來。

--force-with-lease 保護的是「遠端從我上次 fetch 之後有沒有變」。我 fetch 得夠新,所以它認為安全,然後就讓我推了。它擋的是過期的認知,不是併發的寫入。 所以真正的規則是:

force push 前先 git fetch && git log <remote>/<branch> -3 用眼睛看一次遠端最上面三筆是不是你以為的那三筆。

這條到今天都還成立,跟 Cloudflare 沒關係。

三個月後我回頭驗,那條規則早就過期了

當時我在 repo 裡立了一條規則:commit message 一律英文。合理,畢竟推不上去就是推不上去。

今天(2026-08-02)我為了寫這篇回去驗,跑了一次最近的部署紀錄:

success  feat(business-cases): B01 電商金流 04-08 上線——出貨/退款對帳/購物車/庫存/促銷
success  docs(private): 作戰板刷新為唯一板(合併六處 backlog)+ 企業系統縮寫速查卡…
success  docs(private): 訪談結論建卡×2——選型雙軸規律+快取分層第 0 條
success  docs: 訪談潤稿 #04 開場(接手者視角)+worker 分機/Jenkins-Argo 進料登記

八筆連續成功,全是中文,而且塞滿了當初我懷疑的那些全形字元:——×()

而部署機制一個字都沒改:一樣是 cloudflare/wrangler-action@v3、一樣是 pages deploy public --project-name=terryyaowork --commit-dirty=true。我比對過 4 月那版跟現在這版的 workflow,差別只有 branch 名從 v4 改成 main

所以結論很清楚:Cloudflare 那頭修掉了(或 wrangler 的版本推進帶掉了),而我不知道。 那條「強制英文」的規則在我 repo 裡多活了至少兩個月,限制著每一次 commit,防著一個已經不存在的東西。

沒人會發現。因為 workaround 生效的時候是安靜的——它不會壞,只會讓你一直付一點點成本。

所以 workaround 要寫到期日

這是我從這件事拿走的東西,也是我認為多數人做得不夠的地方:繞過去之後,那個繞法要留下一張到期單。

不是註解寫「暫時的解法」——每個人都這樣寫,然後它就永久了。要留的是下次怎麼判定它可以拆掉

欄位我這次該寫但沒寫的內容
繞的是誰的問題Cloudflare Pages deployment API,非我方 code
復驗方式推一筆含全形標點的 commit,看 deploy 是否 success
復驗成本一次 push,失敗就 amend 換英文再推——成本趨近於零
什麼時候復驗下次動 deploy workflow 時;或每季一次

最後那欄是關鍵。這次復驗的成本低到荒謬——推一筆中文 commit 就知道了,壞掉的話 amend 一次就回來。我卻讓那條規則活了三個月,只因為沒有任何一個時刻會提醒我去試。

反過來說,如果復驗成本很高(要重建環境、要跟外部單位申請、失敗會影響 production),那到期單的價值更大:至少下一個人知道「這條規則不是天生的,它有一個可以被推翻的條件」。

祖傳規則之所以沒人敢動,不是因為它多重要,是因為沒人知道當初防的是什麼。你留下的那張到期單,就是在防止自己的決定變成別人的迷信。

相關