Баг, который написал 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 помогает, а где нет.