В MiCode несколько продуктов — среди них AI Budżet и eKsięgowy AI — делаются вместе с агентами Claude Code и работают на одном общем VPS. Правила работы с этим сервером, с git и с документацией проектов у нас были записаны: в файлах CLAUDE.md, в памяти агента, в ранбуках. У записанных правил один недостаток: они работают, только если их прочитать в нужный момент. В Claude Code недавно появились моды (в режиме раннего доступа) — функции-хуки, которые меняют поведение самого агента кодом, а не текстом. За одну сессию мы написали четыре таких мода, общих для всех наших проектов. Ниже — зачем, как они устроены и чего мы от них ждём.

Откуда взялась идея: один вечер, четыре проблемы

Всё началось с обычной задачи: посмотреть телеметрию веб-версии AI Budżet и сказать, что из неё следует. За несколько часов мы наткнулись на четыре вещи, которые были не ошибками в коде, а дырами в процессе.

Первое: агент не мог прочитать данные прода. Классификатор auto mode справедливо заблокировал SSH-запрос к базе, и анализ превратился в пинг-понг: агент готовит команду, человек её запускает и вставляет результат обратно. Первая же команда упала на кавычках во вложенной SSH-команде. На весь анализ ушло четыре таких раунда.

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

Третье: собирая адреса health-check'ов для одного из модов, мы обнаружили, что health-эндпоинт одного из наших проектов уже какое-то время отвечал 404. У проекта был свой uptime-check, но на его результат никто не смотрел.

Четвёртое — правила. На общем сервере один демон Docker обслуживает несколько проектов, поэтому docker volume prune, запущенный ради места на диске, удалил бы базы данных других проектов. Коммиты пишутся по-английски, в формате «ABA-123 Описание» без двоеточия. Перед push нужен pull, потому что в ветку коммитят и автоматы. А после каждой задачи нужно обновить вики проекта — правило, которое появилось потому, что вики, которую никто не обновлял, однажды утверждала, что в приложении 11 ИИ-функций, когда их было 18.

Что такое мод в Claude Code

Мод — это папка с тремя файлами: манифест .claude-plugin/plugin.json, hooks/hooks.json со ссылкой на модуль и сам модуль на TypeScript, который экспортирует функцию register. В ней вызывается on(событие, фильтр, хук). Каждый хук имеет вид ($, e, next): $ — интерфейс движка, e — данные события, а next(e) передаёт событие дальше — другим плагинам и стандартному поведению Claude Code. Хук, который не вызвал next, отвечает сам: например, может отказать в вызове инструмента.

Модуль работает в изолированной среде, без Node и без DOM. Всё, что выходит наружу, идёт через $: HTTP через $.http.fetch, процессы через $.process.run, интерфейс через $.ui.status, $.ui.toast и полосу над промптом, собственные инструменты для модели через $.tool.register. Весь API — это интерфейс, через который две системы обмениваются данными автоматически, без выгрузок в Excel и копирования руками. Полное определение → описан в файле деклараций TypeScript, который движок генерирует под свою версию, — у нас это больше 22 тысяч строк, но в нём достаточно найти имя нужного события. Моды перезагружаются на лету при сохранении файла, а claude plugin validate и claude plugin test проверяют их до загрузки.

МодПроблемаЧто делаетОжидаемый эффект
prod-radarСостояние прода видно, только когда кто-то проверитHealth каждого проекта в строке статуса раз в 90 с, полоса и уведомление при падении, диск VPS, слежение за деплоем после git pushПадение замечено за 90 с работы в сессии, деплой без ручного наблюдения
vps-guardПравила общего сервера и git живут только в памятиОтклоняет разрушительные команды Docker, force-push в защищённые ветки, push без pull и коммиты, нарушающие соглашенияКласс «удалил чужую базу» невозможен по умолчанию
prod-queryАгент не может читать прод, и человек копирует командыИнструмент для модели: один SELECT в транзакции READ ONLY, каждый вызов подтверждает человек, персональные данные маскируютсяАнализ данных за один подтверждённый шаг вместо пинг-понга
finish-taskЗнания о проекте гниют, когда ритуал пропущенПосле коммита с кодом без страницы вики, строки в логе или ключа задачи показывает напоминаниеДокументация обновляется в момент коммита, а не через недели

prod-radar: прод в строке статуса

Первый мод раз в 90 секунд опрашивает health-эндпоинты всех проектов и показывает результат в строке статуса: значок у каждого проекта и заполненность диска сервера. Когда что-то падает, над промптом появляется красная полоса и системное уведомление. Уведомляем только о смене состояния, чтобы не засыпать сессию сообщениями, с одним исключением: проект, который лежит уже при первой проверке, тоже показывается. Будь этот мод раньше, тот самый 404 мы бы увидели в первую минуту сессии.

Диск проверяется раз в 15 минут по SSH командой df в режиме BatchMode, так что мод никогда не повиснет на запросе пароля. Важная деталь конфигурации: радар смотрит на health-эндпоинт, а не на главную страницу. Главная, отданная с CDN или статического контейнера nginx, остаётся зелёной, даже когда API за ней не работает.

Третья часть — наблюдатель деплоя. Мод смотрит на каждый вызов Bash, и когда git push действительно что-то отправил (в выводе есть стрелка, а не «Everything up-to-date»), раз в 30 секунд спрашивает gh run list о запусках GitHub Actions для этой ветки. Берёт только запуски, созданные не раньше чем за минуту до push, и самый новый для каждого workflow. Каждый завершённый запуск — короткое сообщение, провал — ещё и системное уведомление. Когда все закончились, мод ещё раз проверяет health: именно сразу после деплоя эта проверка важнее всего.

vps-guard: правила, которые не зависят от памяти

Второй мод — хук на каждый вызов Bash и PowerShell, который может отказать в выполнении команды. В отказе есть причина, модель читает её и пробует снова, поэтому каждая причина говорит не только «нет», но и «сделай вот так».

Команда, которую агент не выполнит
Причина, которую читает модель
docker volume prune -f
Демон Docker общий для нескольких проектов, а его тома — их базы данных. Удаляйте конкретный том по имени и только после подтверждения человека.
docker image prune -a -f
Неиспользуемые, но помеченные образы — цели отката других проектов. Используйте image prune -f (только висячие).
docker compose down -v
Удаляет тома стека вместе с базой. Используйте down без -v.
git push --force origin development
Переписывает общую историю. Сделайте pull --rebase и обычный push.
git push (на remote новые коммиты)
В upstream N коммитов, которых нет в этой ветке. Сначала git pull --rebase.
git commit -m "ABA-12: ..."
Без двоеточия после ключа задачи: «ABA-12 Сделать что-то».

Проверка перед push выполняет git fetch и git rev-list --count HEAD..@{u}. Если в upstream есть коммиты, которых нет в локальной ветке, push отклоняется с просьбой сделать pull --rebase. Если нет сети или upstream, проверка пропускает команду — fail open, потому что блокировать работу из-за сбоя самого стража хуже. Но если хук выбросит неожиданное исключение, его обработчик .catch отказывает: упавший страж не должен молча пропустить то, что должен был проверить. Force-push без явно указанной ветки тоже отклоняется: по самой команде не видно, какую ветку он перезапишет.

Это регулярные выражения, а не граница безопасности. Они защищают от ошибки, а не от того, кто хочет их обойти. У них бывают ложные срабатывания, и первое поймало нас прямо при написании этой статьи: скрипт, который добавлял на сайт таблицу выше, содержал текст docker volume prune как цитату, и страж его отклонил. Решение было простым — скрипт переехал в отдельный файл, — но хорошо показывает, что правило видит текст команды, а не её намерение.

prod-query: доступ к проду, о котором решает человек

Третий мод регистрирует инструмент для модели, prod_query: один SQL — это язык запросов к базе данных: именно на нём записано «покажи продажи за прошлый квартал». Полное определение →-запрос к выбранной продовой базе. Защита многослойная. Запрос всегда идёт внутри BEGIN TRANSACTION READ ONLY с таймаутом выполнения 20 секунд и ожидания блокировки 2 секунды и всегда заканчивается ROLLBACK. Запросы вида SELECT оборачиваются в LIMIT 500, так что ограничение строк соблюдает сама база.

Одной транзакции только для чтения мало, потому что роль в контейнере Postgres — суперпользователь. Такая роль может через psql запустить оболочку командой с обратной косой чертой, выполнить COPY TO PROGRAM или прочитать файл сервера функцией pg_read_file. Поэтому мод отклоняет обратную косую черту, COPY, файловые функции, удалённые подключения, функции управления сервером, блокировки строк и SELECT INTO, а также больше одной инструкции в вызове.

Главный слой — человек. Каждый вызов показывает цель запроса и его текст в стандартном окне вопроса Claude Code и выполняется только после нажатия «Run». Варианта «разрешать всегда» нет. В результате адреса e-mail и телефоны в международном формате маскируются, а обычные последовательности цифр — нет: это с тем же успехом может быть дата, сумма или идентификатор.

Деталь из процесса: классификатор auto mode заблокировал запись файла с этим инструментом по той же причине, по которой раньше блокировал SSH-запросы. Мы дописали его только после явного согласия человека. Так и должно быть: давать ли агенту доступ к данным прода — решение человека, а не агента.

finish-task: напоминание в момент коммита

Четвёртый мод наблюдает за успешными коммитами. После каждого он читает список изменённых файлов и проверяет, менял ли коммит код (исходники вне папок docs, .claude и .github). Если да, а в репозитории есть вики, он проверяет, затронута ли страница вики и добавлена ли строка в лог. Ещё он проверяет, есть ли в заголовке коммита ключ задачи, — но только в репозиториях, где ключи используются. Привычку мод определяет сам: ключ должен быть хотя бы у половины из последних 20 коммитов.

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

Как мы это строили

Все четыре мода появились за одну сессию Claude Code. Агент прочитал сгенерированный файл деклараций API и примеры, а затем для каждого мода вынес чистую логику в отдельный файл: правила стража, проверку SQL, классификацию коммитов, разбор вывода df и gh. Благодаря этому тесты работают без сети и без процессов. Всего это около 740 строк кода и 23 теста. Каждый мод прошёл claude plugin validate, claude plugin test и строгую проверку типов tsc, а настройки каждого — список проектов, адреса, базы — лежат в одном файле config.ts. Кстати, агенты Claude Code делают у нас не только инструменты, но и промо-видео, отрендеренные кодом.

Ожидаемый эффект

Моды работают несколько часов, поэтому дальше — ожидания, а не измеренные результаты. Через месяц вернёмся с цифрами.

Класс ошибок «удалил чужую базу» или «перезаписал чужой коммит» перестаёт зависеть от памяти и становится невозможным по умолчанию. Падение замечается за 90 секунд работы в сессии, а не случайно. Это не заменяет регулярные uptime-check'и в GitHub Actions: радар работает, только пока сессия открыта. После push не нужно вручную следить за деплоем. Анализ данных прода — один подтверждённый вызов вместо четырёх раундов копирования. Документация обновляется в момент коммита, а не обнаруживается устаревшей через несколько недель.

Что будем измерять: число отказов стража и долю ложных срабатываний, число напоминаний finish-task и сколько из них заканчиваются обновлением вики, число вызовов prod_query и отказов в них, а также время от падения до момента, когда его заметили. API модов в раннем доступе и может меняться между версиями Claude Code, так что моды иногда придётся чинить.

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

Что такое мод в Claude Code?
Это папка с манифестом и модулем на TypeScript, который регистрирует хуки на события Claude Code: вызовы инструментов, отправку промпта, отрисовку интерфейса, старт сессии. Хук может передать событие дальше, изменить его или ответить сам, например отказав в выполнении команды. Мод может добавить свой инструмент для модели, запись в строке статуса, полосу над промптом или команду. API находится в раннем доступе.
Как безопасно дать ИИ-агенту чтение продовой базы данных?
Слоями. Запрос идёт в транзакции READ ONLY с таймаутом и всегда заканчивается ROLLBACK. Поскольку роль в контейнере часто суперпользователь, отдельно отклоняются команды psql с обратной косой чертой, COPY, файловые функции и блокировки. У результата есть лимит строк, персональные данные маскируются. Главное — каждый запрос подтверждает человек, и варианта «разрешать всегда» нет.
Является ли хук, блокирующий команды, защитой от атаки?
Нет. Это регулярные выражения по тексту команды: они защищают от ошибки агента или человека, а не от того, кто хочет их обойти. У них бывают ложные срабатывания — наш страж отклонил скрипт, где опасная команда была лишь цитатой в тексте статьи. Настоящей границей остаются права доступа, резервные копии и подтверждение человека.
Заменяет ли мод мониторинг uptime?
Нет. Мод работает, только пока открыта сессия Claude Code. Регулярные uptime-check'и в GitHub Actions с алертами в Telegram по-прежнему работают круглосуточно. Радар в сессии сокращает время, за которое падение замечают во время работы, и показывает состояние всех проектов в одном месте.