勘誤表 · 2026-07-14 · CODE

我跟 AI 說我很累,它沒有安慰我,它去量了

量出來的答案不是「工作量太大」,是這套系統裡有三層東西在對我說謊,而我是唯一在檢查它們的人。兩天後四件事做完三件——而每挖開一層,底下的東西都比診斷書上寫的更深。最深的那一層是:反幻覺檢查的事實基礎,是幻覺本身。

我一個人在幫幾家民宿管 Google Ads。整套系統——抓資料、分析、每週產一份報告寄給業主、動廣告帳號的設定——是我跟一個 AI agent 一起寫的,現在有幾十支腳本、十幾條 CI 排程在跑。

它替我省了很多時間。但它也讓我很累,而累的原因跟我以為的不一樣。

「82 支腳本、5 個測試檔,而 CI 從來沒跑過任何一個測試。所以每個 bug 都會一路衝到終點——而終點是『報告寄到業主信箱』或『廣告帳號被改掉』。這套系統的唯一測試層,是你。」

—— AI 對這套系統做的結構診斷,2026-07-13

壹 · 始末它不是從一個 bug 開始的

它是從一句話開始的。我打字跟它說:「我覺得最近很累。」

對這句話最自然的回應是同理心。它沒有。它去量了。

量出來的答案不是「工作量太大」,是三層不能信的東西——而我是唯一在檢查它們的人:

一、工具會說謊:沒有壞消息,被當成好消息。 稽核工具只有在「主動炸掉」時才說失敗,其他一律印 ✅。所以檢查了零頁會印「全數達標」;連不上的帳號會被算成正常;被 Google 默默忽略的寫入會印「已清除」。

二、LLM 會編造,而我們用散文跟它對抗。 一份週報裡有 852 個數字,每一個都是 AI 從頭打出來的。為了讓它別打錯,我們寫了 1,664 行 prompt 教它規矩,又寫了一層檢查去抓它編的數字,抓到就重擲骰子。60 天內光這兩處就改了 93 次。而它今天還是出錯了。

三、系統的唯一測試層是我。 那天我一個人抓到六個錯。沒有一個是工具自己發現的。

診斷書最後列了四件該做的事。我們做完了三件。而做的過程中發現——每一件事的真正病因,都不是診斷書上寫的那個。


貳 · 第一個發現問題不是「沒有檢查」,是「檢查沒有牙齒」

診斷的第三層說:危險操作要靠結構安全,不要靠我點頭。我以為要做的事很單純——22 支會改動線上廣告帳號的工具,補上「寫完回頭讀一次」。

然後發現,最重要的那幾支,全都已經有回查了。

它們寫完、重讀、比對、發現不符——然後印一個 ❌,然後 exit 0

ads-mutate.ts —— 改關鍵字的工具。自動化流程真正呼叫的就是它
⚠️  Some changes could not be verified — check above and confirm in Google Ads UI.

process exit code: 0
而呼叫它的機器人只看 exit code,於是在 Issue 上留言:「✅ 處方已套用」。

一個沒生效的關鍵字暫停、一個沒掛上帳號的否定字清單、一個沒關掉的 Google 自動套用——全部都會被回報成「做好了」。

驗證做滿了,只差沒有牙齒。 而這比沒有驗證更危險:它把失敗洗白了。 一份看起來被檢查過的東西,你不會再去檢查第二次。

最赤裸的一行

把圖片掛到廣告活動上的工具
console.log(`created ${res.results?.length ?? 0} link(s) ✅`);
掛了 0 個也印 ✅。而那個 0 不是「零個連結」——results 是 undefined,那個 0 的意思是「我不知道」。而「我不知道」被印成了成功。

還有換網域的工具:它會先刪掉一支正在服務的廣告,再建一支新的。如果「建立」那步靜默失敗,這個廣告群組會完全沒有廣告——而腳本會印「Domain fix complete」。

3
用共用回查零件(22 支寫入工具裡)
6
自己手刻回查(能動,但每支各刻一次)
13
「沒報錯」就當成功
22
現在:22/22 每一支都會回頭讀一次

參 · 第二個發現AI 手查了三次,錯了三次

上面那些,AI 一開始是用 grep 查出來的。而它的 grep 說:

A 工具沒有回查 → 它有(自己手刻的)。

B 工具沒有回查 → 它也有

C 工具沒有回查 → 它有回查,但沒有牙齒

三次,三種不同的錯法。因為每支腳本說謊的方式都不一樣,而人(和 AI)在讀程式碼時,會不自覺地相信它的自我描述。

所以它停止手查了。它寫了一支測試,讓機器問一句定義明確、沒有詮釋空間的話:「你有沒有呼叫那兩個會 throw 的回查零件?」

那支測試裡有一份「還沒接上」的清單——它是債務清單,不是豁免名單。另外兩支測試盯著它:已經修好的還留在上面會紅(債務不准看起來比實際大),把不相干的東西塞進去換綠燈也會紅(不准用它買安靜)。

這是整件事裡我最想留下的一個模式:當你發現自己的判斷不可靠時,不要更努力地判斷——把判斷交給一個問得出定義明確問題的機器。


肆 · 第三個發現我們做的守門,本身就是假綠燈

修的過程中,我們加了一道 CI 關卡:擋「用了沒有 import 的名字」——這一類錯誤打包工具編得過(一個自由變數只是全域查找)、測試蓋不到(它只在被呼叫的閉包裡爆),而它會在執行時炸。對這些工具來說,「執行時」的意思是正在改線上廣告帳號的當下

關卡跑起來,回報:0 個問題。

而其中一支腳本裡就躺著一個不存在的變數——在負責建立新客戶廣告活動的那條路徑上。而且是那天剛寫的。

tsconfig.json
"include": ["src/**/*"],
"rootDir": "./src"
這個專案有 tsconfig.json,但從來沒有裝過 TypeScript。npx tsc 抓到的是一個佔位套件——它印一句「This is not the tsc command you are looking for」然後正常結束。所以「型別檢查說沒問題」這句話,本身就是一個假綠燈。

我們花一整天在追殺「看起來在保護你、實際上什麼都沒看的檢查」——然後發現前一天親手做了一個。


伍 · 最深的一層反幻覺檢查的事實基礎,是幻覺本身

診斷的第二層說「LLM 會編造」。而整套系統對此的防護,是一個叫 verify 的階段:它收集所有可信的數字,然後檢查報告裡的每一個數字是不是都在那個集合裡。找不到的就叫「孤兒數字」,重擲骰子。

那個「可信集合」是這樣建立的:

verify.ts
walkCollect(data);       // 真資料 —— 可信
walkCollect(findings);   // AI 自己的分析輸出 —— 也被當成可信
AI 打一個數字 → 它自動進入可信集合 → 報告引用它 → verify 在可信集合裡找得到 → 放行。

它不可能抓到任何 AI 編的東西——因為 AI 的輸出就是它的標準答案。

把新的檢查套回兩份已經寄出去的報告

一家客戶的健檢報告 · 2026-07-02 · 已寄出

廣告帶來的網站訪問次數 報告寫著「廣告帶來的 58 次網站訪問中,沒有產生任何一次轉換」。

58
59

一個關鍵字的品質分數 3 分和 5 分是完全不同的診斷。3 分的意思是「你的落地頁有問題,去重做」——那是一筆真金白銀的工。

3
5

散文裡的三個數字 「這些點擊花費了超過 NT$1,650,佔總花費近 50%」——AI 自己對搜尋字列表做的加總。可能對。沒有任何東西查過。

1,650
?
另一家客戶 · 2026-06-30 · 已寄出

一個廣告連結的目標網址 真值是一個把付費點擊送去 OTA 平台的網址。而那條發現的「證據」,是數字 0。

0
一個真實的網址

而解法一直就在那裡

AI 產出的每一條證據,本來就已經附了一個「來源路徑」——「這個數字在資料的哪個位置」。

而那些路徑全部都是對的。它指的位置,真的就是那個數字住的地方。

AI 很擅長「指出這個數字在哪裡」。它很不擅長「把它抄對」。

所以:程式去解析那個路徑,用真值覆蓋 AI 打的值。 就算它這次剛好打對——也一樣覆蓋。運氣好不是一個可以放進客戶報告的來源。

然後把「把 AI 的輸出收進可信集合」那一行刪掉。它的分析不是事實來源,它是一個「主張」。

「檢查 AI 打得對不對」是一份永遠追不完的清單。

「讓它打的數字沒有機會進入報告」是不可能發生。

差別是「抓到」跟「不可能」。


陸 · 現在得到什麼四件事,三件完成

AI 不准寫數字

找到根因(防護層信任 AI 自己的輸出)。分析階段的每個數字現在由程式從來源路徑讀回真值;可信集合=真資料 ∪ 確定性計算。每次攔下的幻覺都會印出來——沉默的修正等於沒有修正

完成

「跳過」不准被算成「通過」

零件建好了(分母在檢查前宣告、跳過永遠不是通過、忘記宣告直接 throw)。但不是每支工具都接上了——只有真的會「走訪一組東西然後下結論」的才接。我不打算為了讓數字好看,把它硬塞進不需要的地方。

按需接入

危險操作靠結構安全,不靠我點頭

22 支寫入工具,22 支都會回頭讀一次確認生效。忘了指定帳號會硬失敗(以前會安靜地預設成某一家客戶)。有測試盯著這件事,債務清單只能變小。

22 / 22

機器健康:壞掉的東西自己舉手

不是新腳本,是晨報的一個段落——那封信已經每天寄、已經是心跳。查排程「最後一次成功」(不是「最後一次執行」)、憑證死沒死、報告有沒有真的產出。紅燈會衝上主旨列。

完成
83
腳本峰值(一邊喊太大,一邊養到這裡)
27
現在的工具數,59 個動詞
0
CI 在跑的測試(診斷當天)
145
CI 在跑的測試 + 3 道結構關卡

順便發現的:真正的成本不是行數

合併腳本的時候,AI 一開始以為目標是「少幾行」。我打斷它:

「合併一支帶子指令的工具,行數不變,但你要記的東西從 4 個變成 1 個。那才是真正的成本。」

所以分家族的判準不是名字前綴(光是廣告相關的就有 44 支,那不是一個家族),而是:「要動這個領域的東西,我得從幾個檔案裡把知識拼回來?」

預算有四支腳本,各自握著「Google Ads 預算會怎麼騙你」的一塊碎片。否定字有八支,而「零轉換所以可以擋,是一個自我實現的預言」這句話沒有家。合併不是為了少幾行,是為了讓知識有一個家。

而其中一個家族我們拒絕合併——網站量測的三支工具,資料源分別是瀏覽器實測、Google 的 API、和分析平台。三個資料源、三個問題。併了只是數字好看。


柒 · 留下的四句話

一、「我看不到」不是「它沒事」

查不到的帳號、連不上的 API、跑不起來的查詢——它們不能被算成正常。分母必須在檢查開始之前宣告,每一個項目都得交代成「通過/有問題/沒檢查到」,對不上就是程式 bug,不是稽核結果。

二、沒自測過的偵測器,不能信

這兩天做的每一道守門,都先種一顆假地雷、確認它抓得到,才敢信它。沒這樣做的那幾次,全都錯了——包括那個「說 0 個問題」的型別檢查關卡。

三、防護要主動說出它擋不住什麼

死引用檢查看不到某些檔案——那句話寫在測試自己的檔頭裡,不讓它假裝自己覆蓋了全部。一個誠實宣告自己盲區的防護,比一個看起來全面的防護有用。

四、雜訊會殺死訊號

機器健康那段如果權限沒設對,它會每天誠實地回報「我看不到執行紀錄」。一個天天誤報的監控,兩週後就沒有人在看了——而那時候它跟不存在完全一樣。


捌 · 誠實的結尾它今天推翻了自己六次

這兩天讓我最累的,其實不是 bug 多。是它給我一個結論,後來又推翻。

它把 82 講成 83;用 grep 判斷回查覆蓋率,錯了三次;相信一個自己沒測過的型別檢查關卡;在合併腳本時親手埋了五顆會在正式環境爆炸的地雷——一個看不到的空格,讓工具在執行時才炸,而打包工具看不到、測試蓋不到。其中一顆就在「真的會改廣告」的那條路徑上。

差別在於:這一次,每一個都是機器叫出來的,不是我撞到的。

第一顆地雷是它意外發現的。寫完那支測試之後,剩下的四顆都是測試自己喊的。

所以這兩天真正做完的事,不是把系統修對了。是把「系統騙我」這件事,變得很吵。

以前唯一的偵測器是我。現在偵測器是機器——包括偵測它自己。


所有數字量自我的 repo,2026-07-14;診斷書出自 2026-07-13。客戶名稱已隱去,數字、日期、程式碼片段皆為原樣。

本文所有數字都是程式從 repo 讀出來的——在一篇關於「編造數字」的文章裡編一個數字,會是這兩天最好笑的事。