Назад в блог

Баг, который написал Claude: семь дней в проде

Код выглядел правильно. Тесты были зелёными. CI прошёл без замечаний.

Я нажал Approve.

Через семь дней в продакшне мы нашли баг — ровно там, где я одобрил код, который написал Claude за 11 минут.

Не про то, что AI плохо пишет код. Про то, как меняется режим внимания, когда код пишет не человек — и почему это опаснее, чем кажется.

Что написал Claude и что я проверил

Задача была конкретная: написать пагинацию для REST API Bitrix, которое используется на headless фронтенде на Next.js. Cursor-based — чтобы избежать смещений при живом каталоге.

Claude написал это быстро. Логика выглядела чистой: функция принимала cursor и page_size, возвращала следующий cursor и массив элементов, при достижении конца — null.

Тесты тоже написал Claude. Три сценария: 25 элементов с page_size=10, 7 элементов с page_size=20, 100 элементов с page_size=30. Все проходили.

Я посмотрел на код. Логика cursor — верная. Запросы к API — правильные. Типы — совпадают. Одобрил. Задача закрыта.

Как нашли: через логи на седьмой день

Через неделю поступила жалоба: пользователь зашёл в каталог клиента, добрался до страницы 12 — и увидел пустой список вместо последних товаров.

Я открыл логи. Много запросов на /catalog?page=13, которые возвращали пустой массив. Фронтенд интерпретировал пустой массив не как «конец», а как «нет данных» — и показывал заглушку.

Каталог клиента — 240 позиций при page_size=20. Ровно 12 страниц. Ни одним товаром больше.

Баг: условие окончания пагинации было написано как cursor >= total. Когда cursor достигал ровно 240 — условие срабатывало, но функция уже возвращала пустой массив на последнем шаге, а не null. Фронтенд не различал «пустой последний ответ» и «нет данных».

Если бы каталог содержал 241 позицию или 239 — проблема не проявилась бы. Именно кратность погубила.

Ни один из тестов не использовал кратное количество. 25, 7, 100 — всё некратное. Это не случайность. Тесты написала та же модель, что писала код.

Почему это прошло ревью: режим внимания

Вот что я понял потом.

Когда код пишет коллега — я знаю, где искать. У Петра есть привычка не думать про off-by-one. У Андрея — про порядок операций в async. Я ревьюю не просто «правильно ли», а «правильно ли по меркам конкретного человека».

Когда код написал Claude — я ревьюал иначе. Код выглядел причёсанным. Переменные названы хорошо. Структура читаемая. Нет очевидных лишних шагов. Я проверял логику — не edge cases.

Мозг сделал вывод: «выглядит как будто правильно, значит, скорее всего правильно». И этот вывод прошёл. Потому что AI-код выглядит профессиональнее среднего человеческого кода. Именно это его и делает опаснее при ревью.

Есть термин — automation bias. Склонность доверять автоматизированным системам больше, чем они заслуживают. В случае AI-кода у нас нет десятилетий практики, чтобы сформировать правильный скептицизм.

Логика и edge cases — это разные вещи при ревью

При ревью человеческого кода мы обычно проверяем два уровня: работает ли логика в штатных сценариях и правильно ли обработаны граничные случаи.

AI хорошо справляется с первым. Логика cursor-based пагинации была верной. Структура ответа — правильной. Типы — совпадали.

Но AI оптимизирует под предоставленный контекст. Если в промте не был явно задан edge case с кратным количеством — модель не генерировала сценарий для него. Это не ошибка модели. Это ограничение, которое нужно учитывать в протоколе ревью.

Хороший ревьюер AI-кода должен думать не «правильно ли написана логика», а «какие входные данные могут сломать эту логику при правильной реализации».

Это другой вопрос. И его нужно задавать явно.

Что изменили в протоколе

После того случая мы добавили два шага для любого AI-написанного кода, который идёт в прод:

Первое. Явный список граничных случаев до одобрения. Я буквально пишу: пустой массив, ноль элементов, максимум, кратное количество, один элемент, переполнение типа. И проверяю, что хотя бы часть покрыта тестами — не потому что AI написал плохие тесты, а потому что мы должны задать правильный контекст.

Второе. Явный вопрос модели. Прямо в диалоге: «Перечисли edge cases, которые могут сломать этот код. Какие из них ты не покрыл тестами?» Claude отвечает честно. Чаще всего сам называет кратность или пустые состояния — если спросить прямо.

Это занимает 3–5 минут дополнительно. Не замедляет разработку, но убирает класс багов, которые раньше попадали в прод.

Тесты я всё равно доверяю AI. Но сценарии — диктую сам, хотя бы для критических модулей.

Когда это нормально

Баг в пагинации был некритичным. Никаких данных не потеряно, пользователь просто видел пустую страницу вместо контента. Нашли за 7 дней, починили за 30 минут.

AI-код в продакшне — норма. Главный вопрос не «писал AI или человек», а «кто несёт ответственность за edge cases».

Когда я дал Claude задачу и одобрил PR без явной проверки граничных случаев — ответственность осталась на мне. Не на модели. Инструмент работает так, как его настроили использовать.

С тех пор у меня есть правило: AI пишет код, я задаю граничные случаи. Это занимает больше времени, чем просто нажать Approve. Но именно это и есть ревью.

Семь дней в проде — нормальная цена обучения. Главное, чтобы это не повторялось.


Я веду блог на ivanpin.com, где пишу про headless, Bitrix и работу с AI в реальных проектах. Смотри «Я даю Claude писать тесты. Я не даю ему выбирать архитектуру» — там про то, где AI помогает, а где нет.