三個「點了沒反應」的真實 bug,給 Claude 的 prompt 少一句關鍵描述就定不了位。
Bug 分兩種。有報錯的那種,把 stack trace 丟給 Claude,大概 30 秒給答案。沒報錯的那種——按鈕點了沒反應、頁面不動、表單靜默失敗——Claude 第一反應往往也不對。不是它笨,是它看不到。
最近改 how2claude 的付費流,連續踩了三個這種 bug。覆盤一遍,順便記錄下除錯玄學 bug 時的 prompt 模式。
x402 加密支付接進去,本地跑通了。上正式機一測,每次點「支付」都失敗:前端 console 報 invalid_string at payTo。signing flow 都還沒啟動,facilitator 的 Zod schema 就先拒了。
錢包位址是 0x... 42 字元,肉眼看規規矩矩。我讓 Claude 查 config/credentials/production.yml.enc 裡的 wallet 欄位。
w = Rails.application.credentials.dig(:x402, :wallet_address).to_s
puts "length: #{w.length}"
# => 43
43 個字元。EVM 位址應該是 42。第 43 個字元是中文全形問號 ?(U+FF1F),複製貼上時從中文輸入法帶進來的。
Claude 第一次掃這個欄位時沒發現異常——它看到的是 0x... 開頭的字串,長度對不對它沒主動量。prompt 補一句:「這個位址比預期長 1 個字元,逐字元印出 codepoint」。0xFF1F 一出來,問題立刻定位。
Pricing 頁的 Subscribe 按鈕,點下去頁面不跳轉。沒報錯,network tab 裡 POST 發出去了,Stripe 正常回 302 到 checkout.stripe.com——然後頁面就是不動。
我先讓 Claude 看 controller,邏輯正常:redirect_to session.url, allow_other_host: true。看 JS,沒有相關 listener。
盯了半天回應才發現:Content-Type: text/vnd.turbo-stream.html。Turbo 把 button_to 的提交攔截成 Turbo Stream 請求,而 Turbo Stream 不會跟隨跨來源的 302——302 被吃了,頁面靜默地留在原地。
修復一行:
<%= button_to "Subscribe", ..., data: { turbo: false } %>
同一個 bug 一個月後又在 Google OAuth 按鈕上重現了一次。框架層級的攔截器是玄學 bug 的高發區——Claude 預設會按「請求-回應」線性推理,不會主動去查中間層有沒有改了語義。prompt 補:「這個點擊經過了哪些框架層級的攔截?把請求在瀏覽器→伺服器→瀏覽器全鏈路上被哪些 middleware/JS 處理過的列一下」。
Pricing 頁月付/年付切換的 Stimulus controller,按鈕點了不切。Controller 的方法被觸發了(console.log 有印出來),但 this.monthlyTarget 是 undefined。
Claude 第一反應:target 名字拼錯了。檢查——沒拼錯。data-pricing-target="monthly" 也在 DOM 裡。
問題在 scope。data-controller="pricing" 掛在按鈕容器上,monthly/yearly 那兩塊 grid 在容器外面。Stimulus 只會在 controller 元素內部的子樹裡找 target,外面的找不到。把 data-controller 往上提到包住整個 section 的 <section>,OK。
這個 bug 特別像「程式碼沒錯」——所有名字對、所有屬性都在、功能就是壞的。Claude 預設逐行讀程式碼,不會主動去視覺化 DOM 結構。prompt 補:「把 data-controller='pricing' 這個元素的祖先跟後代畫出來,標出哪些 data-pricing-target 在子樹裡、哪些不在」。
三個 bug 症狀都是「點了沒反應 / 行為不對 / 沒報錯」。Claude 第一反應都不對,但補一句精確的 prompt 之後秒定位。共通套路:
1. 告訴它「預期 vs 實際」的量化差異,別只說「不對」
不是「錢包位址有問題」,是「長度比預期多 1 個字元」。
不是「按鈕不動」,是「回應是 302,但瀏覽器沒跳轉」。
不是「切換失效」,是「controller 方法觸發了,但 target 是 undefined」。
差異越量化,Claude 的搜尋空間越窄。
2. 指向框架層、瀏覽器層、編碼層這些「看不見的層」
玄學 bug 基本都不在業務程式碼裡。在 Turbo、在 Stimulus scope、在字元編碼、在 CSP、在 CORS、在 service worker。Claude 預設只看業務程式碼。要在 prompt 裡明確叫它去看這些層。
3. 要它印出中間狀態,不是推理結論
讓 Claude「逐字元印出 codepoint」「列出回應 header」「dump 整棵 DOM 子樹」——把中間狀態物化出來,比讓它推理結論靠譜。玄學 bug 的「玄」,就玄在推理鏈中某一步有個隱藏前提不成立。物化中間狀態,等於強制把那個隱藏前提攤開來。
有報錯的 bug 考驗的是 Claude 的知識儲備,沒報錯的 bug 考驗的是你給它的訊號品質。你給得越具體,它找得越快。