Фильтр AI-атрибуции в git-хуках: как точное регулярное выражение оказалось точнее, чем нужно
Разработчик обнаружил, что фильтр AI-атрибуции в git-хуках удаляет не только метки вроде «Co-Authored-By: Claude», но и любые коммиты, в которых техническое название инструмента Claude Code встречается как обычное существительное. Ошибка тоньше предыдущей и проще в объяснении.
Регулярное выражение с точным совпадением «claude code» не различает фразу «Generated with Claude Code» и предложение «fix: handle claude code MCP timeout» — оба проходят через один и тот же фильтр, и оба удаляются. Исправление — одна строка, но путь к ней потребовал пересмотра всей логики фильтра.
Предыстория: зачем вообще нужен фильтр атрибуции
В репозиторе, о котором идёт речь, настроен git-хук, который автоматически генерирует сообщения коммитов с помощью CLI-обёртки над Claude (claude -p). Это, по сути, аналог того, что делают Copilot, Cursor и другие AI-ассистенты для разработчиков: смотрят на diff и предлагают однострочное описание изменений.
Проблема в том, что проект придерживается правила «без AI-атрибуции в коммитах». Claude при генерации сообщений по умолчанию добавляет подпись вроде «Co-Authored-By: Claude» или футер «🤖 Generated with Claude Code». Эти метки нужно вырезать, и для этого в коде хука живёт список регулярных выражений _STRIP_PATTERNS.
Неделю назад автор уже исправлял один баг: фильтр использовал простой поиск подстроки, из-за чего коммит вида fix: retry llm calls on 429 with backoff молча удалялся — строка содержала «llm», и фильтр считал это маркером AI-генерации. Замена на регулярные выражения с границами слова (\b) казалась решением. Но «казалось» — ключевое слово.
Где именно сломалось
Текущий набор фильтров выглядит так:
_STRIP_PATTERNS = [
r"co-authored-by\s*:",
r"generated (with|by)\s+claude",
r"\bclaude code\b",
r"\bwritten by (an )?(ai|llm|claude|chatgpt|copilot)\b",
r"\bai-generated\b",
r"🤖",
]
Паттерн r"\bclaude code\b" нацелен на футер атрибуции вида «Generated with Claude Code». Но он не требует, чтобы перед словами «claude code» стояло что-то конкретное — ни предлога «with» или «by», ни глагола «generated». Достаточно просто упоминания инструмента по имени.
А в этом репозитории «Claude Code» — это собственное существительное. Так называется CLI-утилита, вокруг которой построены хуки, скрипты и MCP-сервер. Значительная часть коммитов посвящена именно работе с этим инструментом: исправление таймаутов, добавление документации, настройка воркфлоу для pull request'ов.
Тест: четыре из пяти коммитов уничтожены
Автор проверил фильтр на пяти реалистичных сообщениях коммитов:
fix: retry llm calls on 429 with backoff— проходит (проблема с «llm» уже исправлена).docs: add claude code hook install instructions— удалён.feat: wire up claude code review workflow for PRs— удалён.chore: document claude code slash commands in README— удалён.fix: handle claude code MCP timeout in server.py— удалён.
Ни один из четырёх удалённых коммитов не содержит AI-атрибуции. Все они — обычные технические описания работы с инструментом.
Почему это хуже предыдущего бага
Системный промпт генератора требует от Claude выдать ровно одну строку — без объяснений, без markdown-разметки. Фильтр работает построчно: если строка совпала с паттерном, она удаляется. Поскольку сообщение всегда однострочное, удаление этой строки означает полную потерю текста.
Хук prepare-commit-msg проверяет, не пустое ли сообщение. Пустая строка для него неотличима от ситуации «AI не сработал» или «хук не установлен». Это уже третий механизм, порождающий один и тот же скрытый симптом: рабочий хук, успешный вызов claude -p, реальное сгенерированное сообщение — и ничего в коммите.
Почему нельзя просто удалить паттерн
Первая мысль — убрать r"\bclaude code\b", ведь паттерн r"generated (with|by)\s+claude" должен ловить атрибуцию. Но не ловит. Реальный футер, который выводит Claude Code, выглядит так:
🤖 Generated with [Claude Code](https://claude.ai/code)
Квадратная скобка перед «Claude» разрывает регулярное выражение generated (with|by)\s+claude — оно ожидает пробел между «with» и «Claude», а находит [. Удаление r"\bclaude code\b" устраняет ложные срабатывания, но открывает дыру, через которую реальная атрибуция проходит в коммит незамеченной. Торг одного бага на другой.
Исправление: одна строка с контекстным ограничением
Финальное решение — заменить свободное совпадение на паттерн, требующий предлог перед названием инструмента и допускающий квадратные скобки:
r"\b(with|by|using|via)\s*\[?\s*claude code\]?"
Результаты тестирования:
| Входная строка | Результат |
|---|---|
🤖 Generated with [Claude Code](url) |
Удалён (атрибуция поймана) |
Co-Authored-By: Claude <noreply@anthropic.com> |
Удалён (атрибуция поймана) |
generated by claude code |
Удалён (атрибуция поймана) |
docs: add claude code hook install instructions |
Сохранён |
feat: wire up claude code review workflow for PRs |
Сохранён |
chore: document claude code slash commands in README |
Сохранён |
fix: handle claude code MCP timeout in server.py |
Сохранён |
Паттерн попал в оба файла — server.py и git_commit.py — потому что список _STRIP_PATTERNS дублируется копированием, а не импортом. Автор уже писал о рисках такого подхода к дрейфу кода, и этот случай — ещё одно подтверждение.
Что это значит на практике
Этот баг — частный случай общей проблемы с фильтрами на основе регулярных выражений. Существует два направления тестирования, и оба одинаково важны:
- Поймать всё, что нужно. Фильтр должен срабатывать на реальные варианты атрибуции, включая markdown-разметку, нестандартные пробелы и пунктуацию.
- Не задеть то, что не нужно. Фильтр должен учитывать контекст проекта — в частности, тот факт, что название AI-инструмента может быть техническим термином в кодовой базе, которая этот инструмент использует.
«Я добавил границы слова, значит, теперь точно» — это не проверка. Проверка — это прогнать фильтр через реальную историю коммитов проекта, где слово «claude code» встречается и как атрибуция, и как имя CLI-утилиты.
Важный момент: ситуация, когда название инструмента совпадает с ключевыми словами атрибуции, будет возникать всё чаще. По мере того как AI-инструменты встраиваются в рабочий процесс разработки, их имена неизбежно попадут в сообщения коммитов, pull request'ы, документацию и README. Фильтры, которые не различают упоминание инструмента и подпись инструмента, будут ломаться всё чаще — и всё менее заметно.