AIDive

Четыре гейта Shopify в Claude Code: окупились тесты

AIDive · Опубликовано

Кодинг-агентыАвтоматизация и workflow

Вступление: гейт, который не даёт сказать «готово»

Гейт в том смысле, который вкладывает в него Shopify, это проверка, которую изменение, написанное ИИ, обязано пройти, прежде чем двигаться дальше. После воссоздания в Claude Code окупился только первый из них: приёмочные тесты. Shopify опубликовала четыре гейта и ни одной цифры, поэтому в этом эксперименте они собраны на реальном репозитории, пятьдесят запросов прогнаны с поочерёдно отключённым каждым гейтом, а затем подсчитано, что каждый из них поймал и во что обошёлся.

Helix стал публичным 21 сентября 2026 года. Он пропускает каждый checkpoint через четыре гейта по порядку: тесты, дизайн-ревью, два ревьюера и инженер. В публикации нет ни одного показателя прохождения. Ревьюеров копируют чаще всего, а на этих пятидесяти прогонах показатель прохождения менялся только тогда, когда в цикле были тесты. Ревьюеры не добавили ничего, при стоимости примерно в три раза выше.

С того же стенда получились ещё два вывода. У хука, который якобы нельзя обойти, в его собственной документации есть три выхода. А когда тесты и ревьюеры расходились во мнениях, агент сделал то, чего никто не планировал. Отсчёт начинается со случая вообще без гейта.

Четыре гейта, ноль цифр, один стенд

Репозиторий стенда это tinydb, настоящая документная база данных, на использованном коммите 226 тестов проходят.

Helix это внутренний инструмент Shopify для возврата её мобильных приложений на нативный код. Публикация прямо формулирует правило: каждый checkpoint проходит через четыре гейта по порядку.

Гейт Что он делает в публикации Shopify
Поведение Запускает тесты, где должен пройти каждый относящийся к делу случай
Дизайн Просит вторую модель выступить в роли дизайн-ревьюера-перфекциониста
Ревью Два независимых ревьюера-агента с изолированным контекстом; цикл повторяется, пока оба не одобрят
Инженер Человек замыкает цикл

Фраза, на которой держится вся конструкция: «An attempt is allowed to be wrong. It is not allowed to ship until it isn't.» Для библиотеки дизайн-гейт отпадает, а изменение с одной только логикой, по словам публикации, его просто пропускает.

Чего в публикации нет, так это числа. Приложение Shop было перестроено за 12 недель, а в приложении Shopify больше 300 экранов, и это весь учёт. Нет ни показателя прохождения, ни стоимости, ни числа дефектов, которые проскочили.

Стенд состоит из пяти запросов, сформулированных так, как их формулирует разработчик: операции push и pull, запрос between, переименование таблицы, поиск с сортировкой и размер кэша записи. За каждым запросом стоят пять скрытых требований, которых агент не видит: то, чего ждал бы от работы внимательный старший инженер. Всего получается 25 требований.

Исполнитель это Sonnet 5.5, запущенный в headless-режиме. Пять конфигураций на пять задач на два повтора дают 50 прогонов. Одна оговорка: явные версии этих запросов проходят с первого раза, поэтому формулировки были сокращены до того, что люди пишут на самом деле.

Совсем без гейта проходят 35 требований из 50. Это 70%, при этом три прогона из десяти полностью чистые.

Задача с сортировкой показывает, как выглядят промахи. Агент индексировал каждый документ по полю сортировки, и стоит одному документу этого поля не иметь, как весь поиск падает. Пять требований, ноль пройдено, один отсутствующий ключ.

Хук, который не даёт сказать «готово», и его три выхода

Stop hook это скрипт, который Claude Code запускает каждый раз, когда агент пытается завершить свой ход. Собственное руководство Anthropic называет проблему, на которую он отвечает: Claude останавливается, когда работа выглядит готовой. Без проверки, которую можно запустить, единственным сигналом остаётся «выглядит готовым», и циклом верификации становитесь вы.

Хук живёт в файле настроек проекта, в разделе hooks, Stop, как одна команда с таймаутом. Здесь это однострочная обёртка, которая вызывает скрипт гейта. Контракт задаёт код выхода: 0 даёт ходу завершиться, 2 блокирует его, а всё, что скрипт вывел в стандартный поток ошибок, возвращается агенту как причина.

На практике это выглядит так. Агент подводит итог своего изменения и останавливается. Хук запускает приёмочные тесты, один падает, и ход не завершается. Агент читает сбой как обратную связь от хука, в данном случае это тест, который ожидал ошибку типа и не получил её, исправляет код и снова останавливается. На этот раз гейт молчит.

Правило в промпте это совет. Хук им не является. И всё же та же страница документации перечисляет три пути наружу.

Выход Что говорит документация
Лимит После того как stop hooks продолжили ход 8 раз подряд, Claude Code игнорирует следующую блокировку и завершает ход. Счётчик сбрасывается каждый раз, когда агент вызывает инструмент, а переменная окружения поднимает лимит.
Таймаут 600 секунд по умолчанию для командного хука, а хук, не уложившийся в таймаут, не выносит решения. Медленный набор тестов сам открывает гейт.
Флаг Входные данные хука содержат stop_hook_active, значение true означает, что сессия уже продолжается из-за stop hook. Документация советует проверять его, чтобы никогда не блокировать по условию, которое не может разрешиться.

Гейт здесь записывает этот флаг в лог и игнорирует его. Он сам сдаётся после шести блокировок, чтобы циклы можно было пересчитать.

Есть ещё один выход, о котором никто не пишет в документации. После первой блокировки агент отправился искать тесты: он выполнил cat для скрипта хука и обыскал весь диск в поисках файла с тестами. Остановил его только список разрешений. Поэтому приёмочные тесты лежат вне дерева репозитория, настройки запрещают чтение хука и скрытых тестов, и агент видит сбой, но никогда не видит файл.

Гейт 1: приёмочные тесты

Гейт приёмочных тестов это набор тестов, написанных независимо от исполнителя, которые определяют, что значит «готово». Первый гейт Shopify это не тесты самого агента. Subagent генерирует интеграционные тест-кейсы для каждого checkpoint, настаивая на пограничных случаях за пределами счастливого сценария, и эти тесты определяют, что считается доказанным.

Поэтому автор тестов здесь это отдельный headless Sonnet. Он видит запрос и репозиторий до изменения, только для чтения, не может запускать код, и ему сказано писать по одному тесту на каждое поведение, которого ждал бы внимательный старший инженер: отсутствующие поля, неверные типы, границы, сохранение на диск, кэш и неизменность существующего поведения. Он пишет от 23 до 53 тестов, в среднем 39, примерно за 40 секунд. Файл попадает за пределы репозитория, поэтому при падении теста агент читает утверждение и никогда не читает сам тест.

Одни только тесты поднимают показатель прохождения с 70% до 84%: 42 требования из 50 и шесть полностью чистых прогонов вместо трёх.

Задача Без тестов С тестами
Поиск с сортировкой 5 из 10 10 из 10
Размер кэша записи 8 из 10 10 из 10
Push и pull 4 из 10 4 из 10

Падение на отсутствующем поле в задаче с сортировкой это именно тот случай, о котором спрашивает автор тестов. В задаче с кэшем он потребовал ошибку значения для размера кэша меньше единицы.

Задача push и pull не сдвигается. Она не выполняет три требования, два из них касаются документа без этого поля, а запрос ничего об этом не говорил. Автор тестов страхуется именно там, где промпт молчит: его тест принимает и исключение, и тихое отсутствие действия, поэтому гейт не может восстановить требование, которое никто не формулировал.

Из 30 прогонов с гейтом на тестах гейт возвращал агента 28 раз при реально падающем требовании и пять раз, когда проходили все. Один из этих пяти пришёлся на задачу с переименованием: автор тестов хотел отвергать имена, не являющиеся строками, никто этого не просил, и агент потерял из-за этого один круг.

Первая версия этого гейта перезапускала собственные тесты исполнителя. Она пропускала каждый прогон при первой остановке, пока скрытое требование не выполнялось, потому что тесты агента проверяют то, что он построил.

Цена гейта: $0.20 за прогон вместо $0.085 и 60 секунд вместо 33.

Гейт 3: два ревьюера, как у Shopify

Гейт ревью это пара агентов-ревьюеров, каждый из которых работает в свежем контексте. Формулировка Shopify: два независимых ревьюера-агента с изолированным контекстом проверяют новый код, каждое замечание нужно исправить, а цикл повторяется, пока оба ревьюера не одобрят. Руководство Anthropic объясняет, зачем нужна изоляция: ревьюер в свежем контексте видит только diff и критерии, которые вы ему даёте, но не рассуждения, породившие изменение.

Каждый ревьюер здесь это headless Sonnet с инструментами только для чтения и не более чем 15 ходами. Он возвращает вердикт в виде структурированного JSON: флаг approve и замечания, у каждого из которых серьёзность blocking или minor. Два ревьюера работают параллельно над запросом и полным diff, их замечания объединяются, и гейт блокирует, если хоть один нашёл что-то blocking. Пара обходится от 3 до 6 центов за остановку.

Конфигурация Пройдено требований Показатель прохождения Блокировки
Без гейта 35 из 50 70% нет
Только ревьюеры 34 из 50 68% 1 прогон из 10
Только тесты 42 из 50 84% 6 прогонов из 10 (3 обоснованных, 3 ложных)
Ревьюеры после тестов 41 из 50 82% 2 прогона из 10, оба обоснованные

Только ревьюеры оказываются на два пункта ниже варианта вообще без гейта, а ревьюеры после тестов на два пункта ниже одних тестов.

Эту цифру объясняет задача с сортировкой. В одном прогоне оба ревьюера заблокировали: сортировка индексирует документ, один документ без этого поля вызывает ошибку ключа, и весь поиск падает. Агент это исправил и прошёл пять из пяти.

В двух других прогонах оба ревьюера увидели абсолютно тот же сбой и оценили его как minor. Документ без поля сортировки вызывает голую ошибку ключа, и в docstring это задокументировано. Оба ревьюера одобрили, и оба прогона закончились на нуле из пяти. Та же модель, тот же diff, тот же баг, две разные серьёзности. Ревьюеры никогда не ошибались, когда блокировали. Проблема в том, что они пропускают.

Работа за август измерила ту же картину на публичном бенчмарке. Zero-shot дал 77%, один ревьюер 77%, а два независимых ревьюера 75%, всё в пределах шума относительно отсутствия ревью.

Конфигурация на LiveCodeBench Результат
Zero-shot 77%
Один ревьюер 77%
Два независимых ревьюера 75%
Ревьюер плюс критик (состязательное ревью) 87%

Ревьюер плюс критик и цикл без конца

Состязательное ревью это ревьюер в паре с критиком, который проверяет ревью через структурированное несогласие до того, как основной агент вносит правки. Та же работа показала, что именно эта форма работает. Она дала 87% там, где два ревьюера дали 75%, и обошла сильнейший базовый вариант из пяти агентов с 82%.

Бенчмарк Zero-shot С состязательным ревью Стоимость
SWE-bench 71.6 75.2 около 4.5x токенов

Поэтому критик был добавлен в гейт. Ревьюер A проводит ревью. Критик B получает замечания и diff и помечает каждое как подтверждённое или отклонённое. Гейт закрывается только подтверждённым блокирующим замечанием.

На этом репозитории результат составил 39 из 50, то есть 78%. Пара «ревьюер и критик» не заблокировала ни разу за десять прогонов. При валидации она всё же отклонила ложное замечание, на котором заблокировала бы схема с двумя ревьюерами.

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

Третий случай стоит запомнить. Приёмочный тест требовал, чтобы неудачное обновление оставляло все документы нетронутыми, поэтому агент переписал метод обновления таблицы. Ревьюеры это заблокировали, потому что изменение метода обновления не запрашивали, а задача состояла из двух новых функций. Агент откатил правку. В его последнем сообщении говорилось, что приёмочный тест и ревью кода требуют противоположного, поэтому он остановился, ничего не меняя. Шесть блокировок спустя счётчик его выпустил.

Точка сдачи здесь стоит на шести. Собственный лимит Claude Code это восемь последовательных блокировок со сбросом при каждом вызове инструмента, поэтому он не сработал ни разу за пятьдесят прогонов.

Гейт 4: вы сами, что дошло до человека, счёт и набор

Четвёртый гейт это инженер, и Shopify в своей публикации обещает, что гейты не становятся мягче, когда никто не смотрит. До инженера доходит всё, что пропустили остальные гейты. Таблица ниже отвечает на вопрос, какой гейт окупил себя.

Конфигурация Пройдено из 50 Показатель прохождения Стоимость за прогон Остаётся человеку
Без гейта 35 70% $0.085 15
Приёмочные тесты 42 84% $0.20 8
Только ревьюеры 34 68% $0.15 16
Тесты с ревьюерами 41 82% $0.28 9
Тесты с ревьюером и критиком 39 78% $0.28 11

Каждый прогон с гейтом занимает от 54 до 86 секунд против 33 без гейта.

Блокировки по всем пятидесяти прогонам распределяются так. Тесты возвращали агента 28 раз обоснованно и пять раз ошибочно. Ревьюеры заблокировали три раза, ни разу не ошибочно, и три цикла сдались. Вся матрица обошлась в $9.95 по прейскурантной стоимости API, как её сообщает CLI, и примерно в 3136 секунд, то есть 52 минуты, суммарного времени исполнителей.

Вывод для репозитория вроде этого: оставьте гейт тестов, с автором, который не является исполнителем, и с тестами, которые агент не может редактировать. Ожидайте примерно одну ложную блокировку из шести и дайте циклу счётчик. Гейт двух ревьюеров в том виде, как его описала Shopify, не сдвинул показатель прохождения, ни в одной из двух форм. Если вы его оставляете, оставьте его как совет в обратной связи, а не как планку.

Одна ловушка по пути. Документация описывает голый флаг, который пропускает hooks, skills, plugins и память для скриптовых вызовов. При входе по подписке он читает только ключ API и возвращает ошибку, поэтому каждый ревьюер изолирован иначе: пустым источником настроек и отключёнными хуками.

Ограничения реальны: один репозиторий, одна модель-исполнитель, пять задач, два повтора на ячейку. Разрыв в два пункта между строками это шум. Четырнадцать пунктов между отсутствием гейта и гейтом тестов шумом не являются. Дизайн-гейт не строился, а инженера не хронометрировали.

Из четырёх гейтов Shopify на этом репозитории окупились только тесты. Весь набор лежит в бесплатном пакете: файл настроек с хуком и списком запретов, скрипт остановки, гейт, команды автора тестов и ревьюеров с их схемами и шаблон учёта.

Бесплатные материалы

Источники

Частые вопросы

Какой из четырёх ИИ-гейтов Shopify стоит воссоздавать в Claude Code?
Гейт приёмочных тестов. На 50 прогонах на tinydb он поднял показатель прохождения с 70% до 84%, а гейт двух ревьюеров его не сдвинул (68% отдельно, 82% после тестов).
Как не дать Claude Code сказать «готово», пока тесты не пройдены?
Добавьте Stop hook в настройки проекта, который запускает ваши приёмочные тесты и при сбое завершается с кодом 2. Claude Code блокирует завершение хода и возвращает агенту всё, что скрипт записал в стандартный поток ошибок, как причину.
Можно ли с помощью Stop hook реально заставить Claude Code продолжать работу?
Не полностью. Документация хуков перечисляет лимит в 8 последовательных блокировок, который сбрасывается при каждом вызове инструмента, таймаут по умолчанию 600 секунд, после которого решение не выносится, и флаг stop_hook_active, который советуют проверять. Добавьте собственный счётчик блокировок, чтобы циклы завершались предсказуемо.
Улучшают ли два независимых ИИ-ревьюера кода результат агента?
В этом тесте нет. Два ревьюера на Sonnet дали 68% отдельно и 82% после тестов, на два пункта ниже каждой базовой линии, потому что в двух прогонах оценили настоящий сбой как minor. Работа измерила ту же картину на LiveCodeBench: 77% zero-shot, 75% с двумя ревьюерами, 87% с ревьюером плюс критиком.
Сколько стоит каждый гейт за прогон в Claude Code?
Прейскурантная стоимость API за прогон составила $0.085 без гейта, $0.20 с приёмочными тестами, $0.15 только с ревьюерами и $0.28 с тестами плюс ревьюеры. Прогоны с гейтом занимали от 54 до 86 секунд против 33.
Как не дать агенту прочитать скрытые приёмочные тесты?
Храните тесты вне дерева репозитория и запретите в настройках чтение скрипта хука и файлов тестов. Тогда агент видит упавшее утверждение, но никогда не видит сам файл.

Похожие видео