自動化靜默失敗檢查:排程回報成功卻什麼都沒做,先看這三點
發佈 2026-09-10 · 更新 2026-09-09 · 閱讀約 6 分鐘 · AI 工作術(法芮可有限公司)
排程跑完、節點全綠、通知也發了,資料卻一筆都沒進去——這就是自動化靜默失敗。本文用處理筆數、誰被通知、跑過 vs 跑成功三個檢查點,帶你 15 分鐘自檢爬蟲、AI 自動發文與各種排程,並附可直接照抄的告警與心跳設計、演練方式與常見誤區。
自動化最貴的 bug,不是那種跳紅字的。是它明明什麼都沒做,卻回報「成功」。排程跑完了、流程節點全綠、聊天室還準時推一則「今日同步完成」,結果資料庫一筆都沒進去。這叫靜默失敗(silent failure),它最討厭的特徵就是:所有你平常會看的指標,全部正常。
要抓它,別急著翻 log。先回答三個問題:這次到底處理了幾筆?失敗的時候誰會知道?這支程式是「跑過」還是「跑成功」?這三題答不出來,你的自動化就處在「壞掉了也不會有人發現」的狀態——只是還沒輪到你被抓包而已。
我們自己在營運 AI 工具站、自動剪片與廣告產製流程,前陣子花了一整天把手上幾套後端和排程掃過一遍,光是「例外接住了、寫一行 log、然後照樣往下走」的點就兩百多個,其中五十幾個是會寫資料庫的。真正把我們戳醒的是:那次出事不是監控發現的,是同事發現的。下面這三個檢查點,就是那輪自檢整理出來的最小可行版本,不需要導入任何監控平台,今天就能做。
靜默失敗長什麼樣?為什麼自動化特別容易中
一般的 bug 會炸給你看:程式停住、頁面白掉、Log 一整片紅。靜默失敗剛好相反,它把失敗吃掉,然後對外報成功。最常見的四種寫法:
- 接住例外只寫 log 就走掉:`catch` 裡面只有一行「同步失敗」,沒有重拋、沒有告警,外層繼續往下跑。
- 成功旗標放在錯誤處理外面:寫入包在 try 裡面,`saved = true` 卻寫在 try 外面,寫不寫得進去完全不影響對外回報。
- 把「找不到」當成「成功」:資料庫刪了 0 筆、查詢回空陣列,程式直接 return,後台顯示「已刪除」。
- 對第三方回呼失敗還回 200:金流、物流、社群平台的 Webhook 看到 2xx 就不會重送,那是你最後一次補救機會,被你自己丟掉了。
排程、爬蟲、AI 自動發文這類流程特別容易中,原因很單純:沒有使用者在看畫面。網站壞掉五分鐘就會有人喊,但一支每天早上七點跑的排程,連續兩週抓 0 筆也不會有任何人察覺。而且這類流程多半沒有 HTTP 狀態碼,你唯一的「它還活著」的證據,就是 log 檔的更新時間——而一支每次都炸掉的程式,traceback 也是寫進 log,更新時間看起來跟健康的一模一樣。
| 你以為 | 實際上可能是 |
|---|---|
| 通知說「同步完成」 | 完成了流程,但處理 0 筆 |
| Log 每天都有更新 | 每天都在寫同一段 traceback |
| 排程狀態顯示成功 | 程式 exit 0,但內部逐筆都失敗 |
| CI 是綠燈 | 那條流程根本沒跑測試 |
| 爬蟲有寫檔案 | 寫進去的是空陣列,覆蓋掉舊資料 |
檢查點一:處理筆數——沒有數字的「成功」都不算成功

這是投報率最高的一條,而且不用改架構:凡是自動化流程的對外回報,都必須帶數字。「完成」是描述動作,「筆數」才是描述結果,而靜默失敗剛好就是「動作有做、結果是空的」。
把通知訊息改掉就對了:
- ❌ 「今日資料同步完成 ✅」
- ✅ 「同步完成:掃描 12 個來源,新增 34 筆,略過 2 筆(已存在),失敗 0 筆,耗時 48 秒」
差別在哪?第二種訊息,你掃一眼就知道不對勁——平常新增三十幾筆,今天寫「新增 0 筆」,你會立刻點進去看。第一種訊息永遠長一樣,壞掉了你也看不出來。
0 筆有三種完全不同的意思
這是最容易被混在一起的地方。抓到 0 筆可能是:(1) 今天真的沒有新資料、(2) 來源網站改版,選擇器抓不到東西、(3) 你的篩選條件寫錯,全部被過濾掉。第一種是正常,後兩種是壞了,但在通知上它們長得一模一樣。
解法是把「掃了幾個目標」和「成功處理幾筆」分開報。掃描 12 個來源、新增 0 筆,那大概是真的沒新資料;掃描 0 個來源、新增 0 筆,那是設定檔讀取失敗或清單腐爛了,必須告警。監控程式回報時只要看到「檢查了 0 個目標」就該叫,不能安安靜靜結束。
加兩條門檻,不用寫複雜邏輯
- 連續 N 次 0 筆就告警:通常設 3 次。真的沒新資料連續三天很少見,選擇器壞掉連續三天很常見。
- 比前 7 天平均少 80% 就告警:抓到「部分失敗」用的。爬 10 個來源掛了 8 個、還剩 2 個有資料,總數不是 0,靠前一條抓不到。
還有一條血淚規則:讀取舊狀態失敗時,絕對不可以拿空值繼續寫回去。「讀舊檔 → 合併新資料 → 覆寫」這個流程,如果讀舊檔那步被 catch 吃掉並回傳空陣列,你就會拿一份空的東西覆蓋掉累積好幾個月的資料,而且流程還回報成功。我們就遇過一次,一千多筆資料就這樣蒸發。寧可這一輪不存,也不能毀掉整份檔案。
檢查點二:誰被通知——log 寫在機器上,沒人看就等於沒寫

「我有記 log 啊」是最常見的自我安慰。log 檔要有人打開才叫監控,而現實是沒有人會每天早上主動 ssh 進去 tail 一個檔案。所以規則很硬:禁止「接住例外 → 寫 log → 什麼都不做」。接住之後,下面三件事至少要做一件:
- 重拋或回錯給呼叫端——讓上游知道這件事沒成,該重試就重試。
- 送告警——走一個 Webhook 打進你每天會看的地方:Slack、Lark、Discord、Telegram 都行。
- 落一筆待處理紀錄——寫進一張表,標記「這筆待重跑」,而且要能重跑。
做告警器的四條要求
我們自己的做法是抽一支 `reportSilentFailure(context, err, extra)`,所有刻意要吞的例外一律呼叫它,不准用裸的 log。這樣還有個額外好處:「專案裡哪些地方是刻意吞的」隨時 grep 得出來。告警器本身要滿足:
- 自己絕不 throw:告警器把主流程弄掛,比沒有告警更糟。
- 自己送不出去時要印明顯標記(例如 `[ALERT-DELIVERY-FAILED]`):連告警失敗都靜悄悄,那是套娃式靜默失敗。
- 同指紋去重 + 每小時上限:一支壞掉的端點一分鐘噴三百則,你三天後就會把那個頻道靜音,等於沒有告警。
- 送出前遮蔽敏感資料:別把整包 request body 倒進去。我們掃過一份 error log,裡面躺著幾十筆明文密碼和會員 email,原因就是登入失敗時把整包參數寫進去了。
另外三個會踩的坑
一、log 一定要帶錯誤本體。寫死一句「上傳失敗」等於沒寫,事後完全無法診斷。而且要實際看一行輸出來驗證——有些 log 套件的格式設定只印訊息字串,你傳進去的錯誤物件整個被丟掉,寫的人以為記了,實際上沒有。驗證方式是去看真實輸出,不是看程式碼。
二、錯誤處理那段程式碼自己要防爆。我們遇過錯誤處理中介層去讀一個在特定情況下不存在的欄位,結果處理器自己丟例外,那筆錯誤完全沒進 log——最需要被記錄的那種錯誤,剛好是唯一記不到的。
三、對平台回呼別亂回 200。做社群自動化時特別要注意,平台有自己的重送機制和額度規則,你回 2xx 就代表「我收到了、處理好了」。這塊的細節我們在 IG 留言自動回覆私訊完整流程 裡有展開講,包含防迴圈和額度耗盡時該怎麼回應。
檢查點三:跑過 vs 跑成功——讓 exit code 說實話
排程系統判斷成敗只看一件事:exit code。而大多數人寫排程腳本的方式是「逐筆處理,單筆失敗就 catch 起來繼續下一筆」——立意良好,但如果結尾直接跑完就結束,exit code 永遠是 0,監控看到的永遠是成功,就算 100 筆全掛也一樣。
修法只有一行:
- 逐筆 catch 沒問題,但要累計失敗數。
- 結尾判斷:失敗數 > 0 就以非 0 結束(`sys.exit(1)` / `return self::FAILURE` / `os.Exit(1)`)。
這條規則對「測試」本身同樣適用,而且更容易被忽略。一支印出「有失敗 ❌」然後 exit 0 的測試腳本,它本身就是靜默失敗——誰跑都看到綠色,紅了多久都沒人知道。我們就有一支紅線閘測試,紅了不知道幾個月,因為所有人跑起來都是 exit 0。每支測試結尾請務必 `exit(0 if ok else 1)`,而且整個專案要有一鍵跑全部測試的入口,任一支紅就整體非 0——測試散在各處、要一支一支手動打的,實務上就是沒人會跑。
心跳:失敗才叫是不夠的
「失敗才告警」有個致命破口:如果排程根本沒被觸發,它連失敗的機會都沒有,自然也不會告警。機器重開沒起來、cron 設定被覆蓋、雲端帳號付款失敗導致 CI/CD 整個停擺——我們就親身遇過一次帳號付款問題讓自動化全數沒執行,整整兩天沒人發現,因為畫面上完全沒有紅字。
所以要反過來做心跳(dead man's switch):每次跑成功就打一個時間戳,另一支監控檢查「這個時間戳有沒有超過預期間隔」,超過就叫。邏輯是「該來沒來就叫」,不是「壞了才叫」。實作上再包一層 wrapper,記錄每次的 exit code 與連續失敗次數,連三次非 0 直接升級成緊急告警。
還有兩條很容易反覆踩的:監控器自己也要被監控——新增任何告警元件,同一回合就把它納入既有心跳,並且故意弄壞一次確認真的會叫,沒演練過的告警一律不算數。以及修補沒進版本控制等於沒修:只存在某一台機器磁碟上的修法,多台跑同一套時就會分岔成「你以為修好的 bug 還在另一台上跑」。回報「已修」之前,先確認 commit 了。
15 分鐘自檢:照這張表把你的自動化跑一遍

不用重構,照著驗一次就好。每一項都要親眼看到輸出,看程式碼推測不算。
| # | 檢查項 | 怎麼驗(15 分鐘版) | 沒過的症狀 |
|---|---|---|---|
| 1 | 通知有沒有帶筆數 | 翻最近三則通知,找得到數字嗎 | 訊息永遠長一樣 |
| 2 | 0 筆會不會叫 | 把來源網址改錯,跑一次 | 照樣回報「完成」 |
| 3 | 失敗時 exit code | 跑完馬上 `echo $?` | 內部有錯卻回 0 |
| 4 | 告警真的會送達 | 斷網或改壞 token,故意觸發 | 聊天室毫無動靜 |
| 5 | log 有沒有帶錯誤本體 | 打開 log 檔看實際那一行 | 只有「xxx failed」 |
| 6 | 有沒有心跳 | 把排程停掉一小時 | 沒有任何人發現 |
| 7 | 重跑會不會重複寫入 | 同一批資料連跑兩次比對數量 | 資料變兩倍、重複發送 |
第 7 項特別提醒:沒有冪等就不能重跑。發券、扣款、開發票、發訊息這類動作重跑一次就是真的做了兩次,所以在做「失敗自動重試」之前,先確認有唯一鍵或去重表擋著,否則你只是把靜默失敗換成「安靜地多做了一次」。
五個最常見的靜默失敗現場
一、AI 自動發文只發出半篇或空白。模型回傳被截斷、被安全機制擋掉、或回了一段解釋而不是內容,程式收到的仍然是一個「合法的字串」,長度檢查沒做就直接發出去。至少加一道:字數低於門檻、缺少必要段落標記,就不准發,轉成待審。另外自動選題若沒設計機制,長期會嚴重同質化,這是內容層的隱形失敗,可以參考 用機制標籤法擺脫內容同質化 的做法。
二、爬蟲在來源改版後抓 0 筆,還把空值寫回去。前面講過,這是最會造成永久損失的一種。加上「筆數為 0 就不覆寫」這條硬規則,成本只有一行 if。
三、AI 產圖/產字幕「有東西但是錯的」。檔案有生成、大小正常、流程回報成功,但圖上的中文是錯字、數字被改掉。程式完全看不出來,只能靠人工抽檢或規則比對,細節可以看 AI 出圖中文錯字實測。這也是我們在做 AI 自動剪片 產線時最堅持的一點:輸出品質的關卡不能只靠「有沒有檔案」。
四、API 額度或 token 用完被誤判成「沒有資料」。429、餘額不足、金鑰過期,如果 catch 寫得太寬,全部會被歸成「這批空的」。成本與額度怎麼估、中文為什麼特別吃 token,可以參考 AI token 是什麼。順帶一提:catch 的範圍不可以比「你預期會失敗的那一行」大。本來只想處理「查不到資料」,結果連後面的寫入和記錄失敗一起吞掉,是我們自檢時最常見的一種。
五、同步到另一台機器前沒有先 diff。兩份不一樣可能是新舊之分,也可能是刻意分岔(某台有另一台沒有的功能),直接覆蓋會靜靜砍掉對方專屬邏輯。動手前先確認哪邊才是真實來源,並列出「只有線上有」的檔案清單。想找適合的排程與監控工具,可以逛逛我們整理的 AI 工具箱。
常見問題
Q:我用 n8n、Make 這類無程式碼工具,也會有靜默失敗嗎?
會,而且更容易。這類工具的節點跑完就是綠的,但「回傳空陣列」對它來說也是成功執行。務必在流程末端加一個判斷節點:筆數為 0 或低於門檻就走告警分支,並且確認錯誤處理路徑不是預設的「忽略並繼續」。
Q:每個排程都加告警,會不會被訊息洗版?
會,如果沒做去重和上限的話。建議只在「狀態改變時」通知:從正常變失敗叫一次、從失敗恢復叫一次,中間持續失敗每小時最多一則。日常成功的紀錄改成每天一則摘要,把筆數放在裡面就夠了。被洗版的告警等於沒有告警,因為你一定會靜音它。
Q:只有我一個人的小專案,需要做到這樣嗎?
三個檢查點裡至少做兩個:通知帶筆數和失敗時 exit 非 0,加起來不到半小時,但能擋掉大部分「壞了兩週才發現」的狀況。心跳可以先用免費的外部監控服務代替,不必自己寫。
Q:怎麼確認我的告警真的會叫?
只有一個方法:故意弄壞它一次。把金鑰改錯、把來源網址改成不存在、直接把排程停掉,看通知有沒有進來。沒有實際演練過的告警一律當作不存在——這點沒有捷徑,看程式碼是看不出來的。
Q:「處理 0 筆」到底要不要告警?
看你的資料節奏。如果來源每天穩定有新資料,0 筆就該叫;如果本來就會有空檔,改成「連續 3 次 0 筆」或「掃描目標數為 0」才叫。關鍵是把「掃了幾個目標」跟「成功幾筆」分開記錄,這兩個數字放在一起,你才分得出來是沒東西可做,還是根本沒做。