Free

Налагоджуємо тихі баги з Claude

Три реальні баги, де клік нічого не робить — Claude промахувався, поки одна фраза в промпті не фіксувала відповідь.


Баги бувають двох видів. Ті, що кидають помилку — передаєш Claude стек-трейс і за 30 секунд маєш відповідь. Ті, що не кидають — кнопка, яка нічого не робить, сторінка, яка не рухається, форма, що тихо валиться — у таких Claude помиляється з першої спроби. Не тому що тупий. Бо не бачить.

Нещодавно, збираючи флоу оплати на how2claude, я підряд наступив на три таких баги. Ось розбір і промпт-патерни, які я тепер використовую для тихих багів.

Баг 1: Знак питання повної ширини, що сховався в адресі гаманця

Підключив крипто-платежі 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, справу закрито.

Баг 2: Кнопка Stripe Checkout не реагувала на клік

Кнопка 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-шар, що обробляє цей запит на шляху браузер → сервер → браузер».

Баг 3: Перемикач Monthly/Yearly не реагував

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 знає. Тихі баги перевіряють якість сигналу, який ти йому даєш. Чим конкретніше — тим швидше він знаходить відповідь.