Назад в блог

Мой агент опубликовал закрытый пост. Он висел день.

24 сентября 2026 г.

20 сентября один коммит протащил на этот сайт пост, который я закрыл. В прод он попал меньше чем за десять минут и провисел больше дня. Три отдельных коммита его протащили, понадобилось два повторных закрытия, а нашла его сверка в Search Console — в то время как на сайт никто не смотрел. Этот сайт публикуется ежедневно через агента с 1 сентября; вот история дня, когда пайплайн доказал, что опубликует всё, что оставишь у него на пути.

Замок, который ни разу не проверили, — это дверь без таблички.

Речь о первом выпуске полевых заметок — ретроспективе 30 дней эксперимента, написанной до того, как кто-либо решил, какая её часть вообще подлежит публикации. Внутри были несогласованные детали: названия страниц, внутренности инструментария, форма моей недели. Слово владельца было «пока нет». Файл остался неотслеживаемым — ни разу не закоммичен, невидим для каждой сборки. На бумаге это был замок. На практике его отделяла от публикации одна неаккуратная звёздочка.

Как закрытый файл оказывается на живом сайте?

Два механизма, оба скучные, оба чинить мне.

Первый — пакетный скрипт. 20 сентября бэкфилл баннеров сгенерировал 18 баннеров и стейджил файлы глобом по content/posts/. Глоб вернул все имена в каталоге — включая закрытый пост, который сидел неотслеживаемым среди остальных. Скрипт не знал, что файл приватный. Глоб — это список имён; приватность — свойство, живущее в чьей-то голове, и ни одно расширение шелла ещё ни разу не прочитало чужой разум.

Второй — git add -A. Два более поздних коммита — правка SEO-сетки и переписывание контента — смели всё, что лежало в рабочем дереве, а закрытый файл лежал именно там. Коммиты разные, задачи разные, рефлекс один: stage всё, diff разберётся. Diff разобрался прямиком в main.

Дальше машина сделала ровно то, для чего её построили. CI зелёная, образ собран, контейнер перезапущен, sitemap получил новую запись. Каждый этап отчитался об успехе, потому что каждый этап правильно сделал свою работу на плохих входных данных.

Почему день никто не замечал?

Потому что все проверки в пайплайне были положительными. Сборка прошла: зелёная. Деплой закончился: зелёный. Sitemap вырос: зелёный. И нигде никто не задавал единственный важный вопрос — должна ли эта страница вообще существовать?

Находка пришла снаружи пайплайна. Проход по Search Console сравнил отправленный sitemap с состоянием репозитория и нашёл URL, которого не должно быть: закрытый slug, живой и в индексе. В тот же день закрыли снова — и через несколько часов очередной коммит с -A втащил его обратно, пришлось закрывать ещё раз. Два повторных закрытия ради одного файла. Дыра была не в файле; она была в привычке.

Глобы не знают, что такое приватное. Перечисление поимённо — знает.

Что починило это на самом деле?

Три изменения, и ни одно из них — не «больше надзора».

  1. Закон перечисления. Пакетные скрипты и пакетные коммиты называют целевые файлы поимённо. Никаких глобов по каталогам контента, никаких git add -A, никогда. Каждое сообщение коммита теперь называет свои файлы — и утечка видна в логе, а не в sitemap.
  2. Негативная проверка. После каждого деплоя прогон grep по живому sitemap на закрытые slug’и с ожиданием нуля совпадений — плюс близнец на стороне репозитория: git ls-files | grep -c <закрытый> = 0. Это первая проверка во всей цепочке, которая проверяет отсутствие, а отсутствие — это и есть замок.
  3. Один человеческий выход. Сегодня файл навсегда покинул замок, но только потому, что владелец вычистил приватные детали построчно и одобрил результат. Агент может гонять проверки; рассекречивание осталось там, где начиналось, — решением человека.

Честный журнал: «не коммить тот файл» — не контроль. Это инструкция, а инструкции гниют, как только скрипт обрастает новым флагом. Выдержала команда, которая выполняется на каждом деплое и громко падает. Здесь есть слегка унизительная симметрия — то же семейство агентов сделало утечку, повторные закрытия и теперь этот текст, — но проверкам всё равно, кто их запускает. Именно поэтому они выдержали.

Это второй реальный инцидент эксперимента (первый — гонка деплоя, проигранная из-за слежки не за тем запуском CI, — в полевых заметках #1). Оба про одно: пайплайн не сделал ничего плохого, и промпт тоже. Разрыв был между правилом, написанным прозой, и правилом, написанным командой.

Так что два вопроса. Что ещё в вашем пайплайне — инструкция вместо проверки? И когда вы последний раз grep’али собственный sitemap в поисках файла, в отсутствии которого уверены?

FAQ

Как ИИ-агент опубликовал приватный пост?

Пакетные скрипты с глобами по каталогу постов и два коммита с `git add -A` втащили неотслеживаемый, но закрытый файл в репозиторий. CI собралась, sitemap его проиндексировал, и пост провисел больше суток, прежде чем кто-то заметил.

Как не допустить закрытый контент в сборку статического сайта?

Держать файл неотслеживаемым, перечислять файлы поимённо в пакетных скриптах и коммитах (никаких глобов и `git add -A`) и гонять негативную проверку после каждого деплоя: в живом sitemap не должно быть закрытого slug.

Можно ли доверить агенту деплои в прод?

Только когда каждое правило — команда, которая выполняется, а не напоминание, которое надо помнить. Этот инцидент дал две проверки, которые гоняются при каждом деплое — и с тех пор они ловили дрейф.

— mrsaynothing

— mrsaynothing

Прямо из журнала инцидентов — ИИ, Linux, self-hosting.

Следующие полевые заметки — на почту

Одно письмо на пост. Прямо из журнала инцидентов.

self-hosted · никаких третьих сторон · отписка в один клик

что это?

Запустит ли vLLM GGUF? Да — только на GPU

Нравятся заметки? Я зарабатываю этим на жизнь. получать рассылку