Три реальні баги, де клік нічого не робить — Claude промахувався, поки одна фраза в промпті не фіксувала відповідь.
Баги бувають двох видів. Ті, що кидають помилку — передаєш Claude стек-трейс і за 30 секунд маєш відповідь. Ті, що не кидають — кнопка, яка нічого не робить, сторінка, яка не рухається, форма, що тихо валиться — у таких Claude помиляється з першої спроби. Не тому що тупий. Бо не бачить.
Нещодавно, збираючи флоу оплати на how2claude, я підряд наступив на три таких баги. Ось розбір і промпт-патерни, які я тепер використовую для тихих багів.
Підключив крипто-платежі x402. Локально працювало. Перший клік у продакшені: invalid_string at payTo у консолі. Signing flow навіть не стартував — Zod-схема facilitator'а відхилила запит раніше.
Гаманець — адреса 0x... з 42 символів, на око бездоганна. Попросив Claude перевірити поле 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-й символ — китайський знак питання повної ширини ? (U+FF1F), заліз під час копіпасту з китайського методу введення.
Перше сканування Claude нічого не позначило — для нього це був рядок, що починався з 0x і виглядав правильно. Він не порахував довжину за власною ініціативою. Додай до промпту: «ця адреса на один символ довша, ніж очікується — виведи кожен codepoint окремо». Вилазить 0xFF1F, справу закрито.
Кнопка Subscribe на сторінці тарифів — клікаєш, сторінка не рухається. Без помилок. Вкладка network показувала, що POST йде, Stripe повертає 302 на checkout.stripe.com — а потім... нічого.
Спершу доручив Claude подивитися на контролер. Логіка ок: redirect_to session.url, allow_other_host: true. JS — жодного релевантного listener'а.
Врешті помітив заголовок відповіді: Content-Type: text/vnd.turbo-stream.html. Turbo перехоплював submit від button_to як Turbo Stream запит, а Turbo Stream не слідує за cross-origin 302 — редирект ковтався, і сторінка мовчки лишалася на місці.
Фікс:
<%= button_to "Subscribe", ..., data: { turbo: false } %>
Той самий баг через місяць знову вдарив по кнопці Google OAuth. Перехоплювачі рівня фреймворка — благодатне поле для тихих багів. Claude за замовчуванням міркує лінійно «запит/відповідь» і не піде шукати проміжний шар, який переписав семантику. Додай до промпту: «пройдись по кожному перехоплювачу рівня фреймворка, через який проходить цей клік — перелічи кожен middleware/JS-шар, що обробляє цей запит на шляху браузер → сервер → браузер».
Stimulus-контролер для перемикання місячного/річного тарифу на сторінці цін — жмеш кнопку, нічого не перемикається. Метод контролера спрацьовував (підтверджено console.log), але this.monthlyTarget був undefined.
Перший здогад Claude: опечатка в імені target. Не було. data-pricing-target="monthly" був у DOM.
Проблема була в scope. data-controller="pricing" висів на контейнері кнопки перемикача, а дві секції сітки лежали поза цим контейнером. Stimulus шукає target лише в піддереві елемента контролера; зовнішні для нього не існують. Підняв data-controller на <section>, що обгортає все — полагодилося.
Цей баг кричить «код правильний» — усі імена збігаються, всі атрибути на місці, просто фіча не працює. Claude за замовчуванням читає код порядково; він не буде візуалізувати структуру DOM з власної ініціативи. Додай до промпту: «намалюй дерево предків і нащадків елемента з data-controller='pricing' — познач, які data-pricing-target потрапляють у піддерево, а які ні».
Три баги ззовні виглядали однаково: клік, нічого не відбувається, без помилок. Claude щоразу першим здогадом промахувався, і щоразу одна додаткова фраза в промпті фіксувала відповідь. Спільний патерн:
1. Скажи йому кількісну різницю між очікуваним і реальним — не просто «неправильно»
Не «з адресою гаманця проблема», а «вона на один символ довша за очікувану».
Не «кнопка не працює», а «відповідь 302, але браузер за нею не пішов».
Не «перемикач зламаний», а «метод контролера спрацьовує, але target undefined».
Чим вужча дельта, тим менший простір пошуку в Claude.
2. Направ його на невидимі шари — фреймворк, браузер, кодування
Тихі баги майже ніколи не живуть у твоєму бізнес-коді. Вони живуть у Turbo, у scope Stimulus, у кодуванні символів, у CSP, у CORS, у service worker'ах. За замовчуванням Claude читає твій код. Скажи йому явно: іди і подивись на ці інші шари.
3. Проси проміжний стан, а не висновки
«Виведи кожен codepoint.» «Перелічи заголовки відповіді.» «Зроби дамп піддерева DOM.» Матеріалізуй проміжний стан замість того щоб просити Claude домислити до відповіді. «Тиха» частина тихого бага — це те, що в ланцюгу міркувань є один крок із прихованим припущенням, яке не виконується. Матеріалізація проміжного стану — це спосіб змусити це припущення вилізти на поверхню.
Помилки перевіряють, що Claude знає. Тихі баги перевіряють якість сигналу, який ти йому даєш. Чим конкретніше — тим швидше він знаходить відповідь.