Free

ดีบักบั๊กเงียบกับ Claude

สามบั๊กจริงที่คลิกแล้วไม่เกิดอะไร — Claude เดาผิดทุกครั้งจนกว่าจะเติมประโยคเดียวใน prompt


บั๊กมีสองแบบ แบบที่โยน error — เอา stack trace ส่งให้ Claude แล้ว 30 วินาทีได้คำตอบ แบบที่ไม่โยน — ปุ่มที่กดแล้วไม่มีอะไรเกิดขึ้น หน้าที่ไม่เลื่อน ฟอร์มที่ล้มแบบเงียบ ๆ — พวกนี้ Claude เดาผิดตั้งแต่ครั้งแรก ไม่ใช่เพราะมันโง่ เพราะมันมองไม่เห็น

เพิ่งเจอแบบนี้สามตัวติด ๆ ตอนทำ flow การจ่ายเงินของ how2claude นี่คือ post-mortem และ prompt pattern ที่ตอนนี้ใช้กับบั๊กเงียบ

Bug 1: เครื่องหมายคำถาม fullwidth แอบอยู่ในที่อยู่ wallet

เสียบ x402 crypto payment เข้าไป local ใช้ได้ Production คลิกครั้งแรก: invalid_string at payTo ขึ้นใน console. Signing flow ยังไม่ทันเริ่ม — Zod schema ของ facilitator ปฏิเสธ request ไปก่อน

Wallet เป็นที่อยู่ 0x... 42 ตัวอักษร ดูด้วยตาแล้วเรียบร้อย ผมให้ Claude ตรวจ field wallet ใน config/credentials/production.yml.enc:

w = Rails.application.credentials.dig(:x402, :wallet_address).to_s
puts "length: #{w.length}"
# => 43

43 ตัวอักษร ที่อยู่ EVM ต้องเป็น 42 ตัวอักษรที่ 43 คือเครื่องหมายคำถามจีน fullwidth (U+FF1F) ที่เข้ามาตอน copy-paste จาก input method ภาษาจีน

การสแกนครั้งแรกของ Claude ไม่ได้ปักธงอะไร — สำหรับมันเป็นสตริงที่ขึ้นต้นด้วย 0x และดูถูกต้อง มันไม่ได้นับความยาวเอง เติมประโยคนี้ใน prompt: "ที่อยู่นี้ยาวกว่าที่คาดไว้ 1 ตัว — print codepoint ทีละตัว" พอ 0xFF1F โผล่ก็จบ

Bug 2: ปุ่ม Stripe Checkout กดแล้วไม่ตอบสนอง

ปุ่ม Subscribe ในหน้า pricing — กดแล้วหน้าไม่เปลี่ยน ไม่มี error. Network tab เห็น POST ออกไป Stripe ตอบ 302 กลับมาที่ checkout.stripe.com — แล้ว... ไม่มีอะไร

ผมให้ Claude ดู controller ก่อน logic ปกติ: redirect_to session.url, allow_other_host: true. JS — ไม่มี listener ที่เกี่ยวข้อง

สุดท้ายสังเกตเห็น header ของ response: Content-Type: text/vnd.turbo-stream.html. Turbo ดัก submit ของ button_to เป็น Turbo Stream request แล้ว Turbo Stream ไม่ follow 302 ข้าม origin — redirect ถูกกลืน หน้าเลยเงียบสนิท

Fix:

<%= button_to "Subscribe", ..., data: { turbo: false } %>

บั๊กเดียวกันนี้เดือนต่อมาโดนอีกที่ปุ่ม Google OAuth. Interceptor ระดับ framework คือแหล่งกำเนิดของบั๊กเงียบ — Claude default คิดแบบ request/response เส้นตรง ไม่ไปหาว่ามี layer กลางที่เขียนความหมายใหม่ เติม prompt: "เดินผ่าน interceptor ระดับ framework ทุกตัวที่คลิกนี้ผ่าน — list middleware/JS layer ทุกตัวที่ประมวลผล request นี้บนเส้นทาง browser → server → browser"

Bug 3: Toggle Monthly/Yearly กดแล้วไม่ตอบสนอง

Stimulus controller ของ toggle รายเดือน/รายปีในหน้า pricing — กดปุ่มแล้วไม่สลับ Method ของ controller fire แล้ว (ยืนยันด้วย console.log) แต่ this.monthlyTarget เป็น undefined

ทายแรกของ Claude: ชื่อ target พิมพ์ผิด ไม่ใช่ data-pricing-target="monthly" อยู่ใน DOM

ปัญหาอยู่ที่ scope. data-controller="pricing" ติดอยู่บน container ของปุ่ม toggle แต่สอง section ของ grid อยู่ นอก container นั้น Stimulus หา target เฉพาะภายใน subtree ของ element controller เท่านั้น ที่อยู่ข้างนอกไม่มีอยู่สำหรับมัน ยก data-controller ขึ้นไปที่ <section> ที่ห่อทั้งหมด — หายปัญหา

บั๊กนี้ตะโกนว่า "โค้ดถูกต้อง" — ชื่อตรงทั้งหมด attribute ครบทั้งหมด feature แค่พัง Claude default อ่านโค้ดทีละบรรทัด มันจะไม่ไป visualize โครงสร้าง DOM เอง เติม prompt: "วาดต้นไม้ ancestor และ descendant ของ element ที่มี data-controller='pricing' — mark ว่า data-pricing-target ตัวไหนตกอยู่ใน subtree และตัวไหนไม่"

สาม prompt pattern สำหรับบั๊กเงียบ

บั๊กทั้งสามจากภายนอกดูเหมือนกัน: คลิก ไม่เกิดอะไร ไม่มี error. Claude เดาผิดทุกครั้ง และทุกครั้งประโยคเพิ่มประโยคเดียวใน prompt ล็อกคำตอบได้ pattern ร่วม:

1. บอกส่วนต่าง เชิงปริมาณ ระหว่างคาดหวังกับจริง — ไม่ใช่แค่ "มันผิด"

ไม่ใช่ "ที่อยู่ wallet มีปัญหา" แต่เป็น "ยาวกว่าที่คาด 1 ตัวอักษร"
ไม่ใช่ "ปุ่มไม่ทำงาน" แต่เป็น "response เป็น 302 แต่ browser ไม่ follow"
ไม่ใช่ "toggle พัง" แต่เป็น "method ของ controller fire แต่ target เป็น undefined"

ส่วนต่างแคบเท่าไหร่ พื้นที่ค้นหาของ Claude ก็แคบลงเท่านั้น

2. ชี้ไปที่ layer ที่มองไม่เห็น — framework, browser, encoding

บั๊กเงียบแทบไม่อาศัยอยู่ใน business code ของคุณ มันอยู่ใน Turbo, ใน scope ของ Stimulus, ใน character encoding, ใน CSP, ใน CORS, ใน service worker. Default ของ Claude คืออ่านโค้ดของคุณ สั่งมันตรง ๆ ให้ไปดู layer เหล่านั้น

3. ขอ state กลาง ไม่ใช่ข้อสรุป

"Print codepoint ทีละตัว" "List header ของ response" "Dump subtree ของ DOM" ทำให้ state กลางเป็นรูปธรรม แทนที่จะให้ Claude ใช้เหตุผลไปถึงคำตอบ ความ "เงียบ" ของบั๊กเงียบคือ step หนึ่งในห่วงโซ่การใช้เหตุผลมีสมมติฐานที่ซ่อนอยู่ซึ่งไม่เป็นจริง การทำให้ state กลางเป็นรูปธรรมคือวิธีบังคับให้สมมติฐานนั้นโผล่ออกมา


Error ทดสอบว่า Claude รู้อะไร บั๊กเงียบทดสอบคุณภาพสัญญาณที่คุณให้มัน ยิ่งเจาะจง มันยิ่งหาคำตอบได้เร็ว