2026年4月21日 星期二

為什麼 OpenClaw 排程一直送不出訊息?一次追到 LINE 429 的原因


 

最近我排查了一個很典型的通知系統問題。

表面上看,OpenClaw 的排程有正常執行,網站資料也抓得到,但 LINE 群組始終收不到通知。更容易誤導的是,部分 run 還顯示 ok,讓人一開始直覺懷疑是網站解析、排程設定,或 prompt 寫法出了問題。

但一路拆解之後,最後真正的根因不是 cron,也不是解析流程,而是 LINE 官方帳號本月免費訊息額度用完,導致主動發送持續被 429 擋下來

這篇整理成一篇偏技術部落格風格的精簡版記錄,聚焦在排查順序、關鍵判斷點,以及最後如何收斂到真正的根因。


問題現象

當時的需求很單純:

  • 定時執行 OpenClaw cron
  • 檢查某個公告頁面
  • 若有新公告,就主動發送到 LINE 群組

實際看到的症狀是:

  • cron 有跑
  • 群組沒收到訊息
  • 部分 run 顯示 ok
  • 部分 run 出現:
  • HTTPFetchError: 400 -
  • HTTPFetchError: 429 -
  • deliveryStatus: not-delivered

這類問題最麻煩的地方在於,系統看起來像只有一個問題,實際上往往是多層故障疊在一起


第一步,先修排程邏輯,而不是先猜平台壞掉

這次最先修的不是 LINE,而是排程本身的送訊邏輯。

當時的設計同時存在:

  • 任務內自己嘗試送訊
  • 外層 delivery/announce 機制也可能介入

這會產生一個典型問題:

  • 任務內若沒有真的送成功,只是輸出「原本應該送什麼」
  • 外層又可能把這段內容當成待送摘要處理

結果就會變成:

  • 看起來像有處理內容
  • 但其實沒有真正送出
  • 還可能多製造一層錯誤

所以第一個修正方向,是把送訊責任收斂成單一來源,避免任務內邏輯和外層 delivery 機制互撞。


第二步,先保證資料一致性

下一步不是硬把通知送出去,而是先修正這件事:

送訊失敗時,不能把資料誤記成已送。

因此我重寫了排程 prompt,核心原則是:

1. 有新公告才處理

2. 只有確認送出成功後,才更新正式已送記錄

3. 如果送訊失敗或不確定是否送達:

  • NO_REPLY
  • 不更新記錄檔
  • 不輸出「應送內容」摘要

這一步很重要,因為它把問題先從「可能污染資料」縮小成「純粹的發送失敗」。

後來做受控測試也確認:

  • 即使 LINE 發送失敗
  • 正式記錄檔也不會被誤回寫

這代表資料一致性已經被保住。


第三步,確認問題不是「沒送」,而是「送了但失敗」

一開始最容易誤判的地方是:

  • run 顯示 ok
  • 但群組沒收到訊息

這時候至少有兩種可能:

1. 任務根本沒送

2. 任務有送,但送失敗後被正確處理掉了

後來直接看 transcript 才確定:

  • 任務內確實有嘗試送 LINE
  • 但送訊時碰到 429
  • 然後依規則 NO_REPLY
  • 所以 run 還是可能顯示 ok

這一步很關鍵,因為它把問題收斂成:

cron 執行流程沒壞,真正失敗的是 LINE 發送端。


第四步,從 log 看出這不是單一 job 問題

接著去看 gateway log,很快就發現不是只有某一支 cron 在失敗,而是整條 LINE 通道近期都反覆出現:

  • line final reply failed: HTTPFetchError: 429
  • delivery-recovery: Retry failed for delivery ...: 429 -

這代表問題已經不能只看成單一排程故障,而要往更外層想:

  • 是不是 LINE channel 本身有問題?
  • 是不是平台在限流?
  • 是不是有 backlog 不斷重試?

第五步,找到 delivery backlog 的實體位置

後來我把 backlog 的實體位置挖出來了:

  • ~/.openclaw/delivery-queue/

裡面會留下待送但未成功的 delivery 項目,內容包含:

  • 舊通知
  • 測試訊息
  • 失敗通知

再結合 log 可以看出:

  • 某批 pending delivery 會被 delivery recovery 拿出來重試
  • 重試又再次碰到 429
  • 於是 queue 一直堆著、一直重試

這說明 backlog 的確存在,而且是跨時間持續存在。


第六步,隔離 backlog,但問題仍然存在

為了驗證 backlog 是否為主因,我採用比較保守的方式處理:

  • 停 gateway
  • 把 active queue 裡的 backlog 隔離到 quarantine 資料夾
  • 再啟動 gateway
  • 重新測試直接送訊

這種處理方式的好處是:

  • 不直接刪資料
  • 可回復
  • 能快速驗證 backlog 是否造成主要影響

但測完後結果很清楚:

  • backlog 確實被隔離了
  • queue 也乾淨很多
  • 但新的直接送訊仍然回 429

這表示 backlog 不是唯一問題,真正更底層的限制還在。


最後根因,LINE 免費訊息額度已用完

最後到 LINE Developers / 官方帳號後台確認,答案就很明確了:

  • 本月免費訊息額度已經用完

這時整個事件才完整串起來:

1. 主動發送額度先耗盡

2. 所有新的主動送訊都回 429

3. 送失敗的內容堆進 delivery queue

4. recovery 不斷補送

5. 補送再度 429

6. 表面上看起來像 cron 有跑但訊息永遠送不到

所以最後真正的根因不是:

  • cron 壞了
  • 網站解析壞了
  • OpenClaw 不會送 LINE

而是:

LINE 官方帳號的主動發送額度先耗盡了。


這次最值得保留的排查方法

這次事件裡,我自己覺得最值得保留的是下面這幾個排查習慣。

1. 不要把所有症狀都叫做「排程失敗」

要拆開看:

  • cron 有沒有跑
  • 任務邏輯有沒有走到送訊
  • delivery 狀態是不是 not-delivered
  • gateway log 在報什麼
  • backlog 有沒有持續重試
  • 平台本身是不是有限流或額度問題

2. 先修資料一致性,再修通知能力

先保證「失敗不會誤標成已送」,再處理送訊本身,這樣排查才不會把資料一起搞亂。

3. ok 不等於真的送到

這次最迷惑人的點就在這裡。run status 只能代表任務流程是否成功收尾,不能單獨代表通知真的送達。

4. backlog 一定要找實體位置

只看 log 很容易一直猜。真的找到 queue 檔案位置後,整個問題才變得可驗證、可操作。


遇到類似問題時的排查順序

如果你也遇到「排程有跑,但通知沒到」的問題,我會建議照這個順序查:

1. 確認 cron 是否真的有執行

2. 確認任務是否真的走到送訊邏輯

3. 確認失敗時不會誤回寫 sent 記錄

4. 查看 gateway log 裡的 delivery / 429 / 400 訊號

5. 檢查 delivery queue 是否有 backlog

6. 如果 backlog 清掉後仍然 429,就直接查平台額度或限流

這樣可以避免你一直在本地系統裡打轉,卻忽略真正的上游限制。


最後怎麼處理?

既然已確認這個月免費訊息額度用完,那在不付費前提下,最實際的作法不是硬送,而是:

  • 暫停主動 LINE 發送
  • 排程改成只記錄待通知內容
  • 同時輸出:
  • 一份人類好讀的 Markdown 待通知檔
  • 一份結構化 JSON 待通知檔
  • 等下個月額度重置後,再切回主動推播

這樣做的好處是:

  • 不再持續撞 429
  • 不再製造新的 delivery backlog
  • 不會遺失新公告資訊
  • 下個月可以平順恢復

結語

這次的問題很適合提醒自己一件事:

排程有跑,不代表通知真的有送到。

很多時候,問題根本不在 cron,而是在更外層的 delivery 設計、queue 重試機制,甚至平台額度本身。

如果我只停在「cron 顯示 ok」這一層,就永遠找不到真正原因。

這次最後能收斂到 LINE 免費額度用盡,靠的不是單一招,而是一路把每一層拆開來看,最後才把問題釘死。

如果你也在做 OpenClaw、LINE 通知、或任何有 queue / retry / 平台額度限制的系統,我很推薦保留這種排查思路。下次再遇到類似問題,會快非常多。

沒有留言: