Два бага в Zulip и спор с ИИ: когда агент ускоряет охоту, но не выбирает фикс

Соло-разработчик с инструментами ИИ-агентов в цикле охоты за багами может быстрее пройти клонирование, поиск по коду и тестовые сьюты — но политику отказа и границы «чей PR не трогать» задаёт только человек. История участника DEV Summer Bug Smash (спонсор Sentry) в кодовой базе Zulip — про два «переоткрытых» бага в Slack importer, этику заявки и отказ от патча в одну строку, который предложил ИИ-дебаггер Seer у Sentry.
Тихая порча данных: где агент помогает искать, а не чинить
Автор p0rt выбрал Zulip из suggested list челленджа: Python, жёсткий quality gate (coverage, mypy, ruff). Фокус — import path: hostile inputs и one-shot migration, где один edge case на сообщении 31 000 из 50 000 роняет весь импорт.
Механическую часть разведки он вёл через агентские инструменты (конкретный продукт в материале не назван). Нашлись два бага без исключений и предупреждений — мигрированный архив выглядит нормально, но ответы в тредах уезжают не туда.
Баг 1 — состояние треда не переживает границы чанков. Slack-сообщения идут чанками по 1000 (MESSAGE_BATCH_CHUNK_SIZE = 1000 в issue #39650). Карта маршрутизации ответов к топикам Zulip жила внутри функции на один чанк: корень треда в чанке N, ответы в N+1 — ответы попадают в сиротский топик вида «… No channel message». Соседний кэш в том же файле сделан глобальным на уровне модуля с комментарием о переживании вызовов — однотипный кэш так не получил.
Баг 2 — ключ треда усечён до секунд. Идентичность треда = strftime("%Y/%m/%d %H:%M:%S") плюс parent user id, без канала и микросекунд, хотя Slack ts несёт их строкой. Два бота, постящих в одну секунду, сливаются в один топик.
Перед фиксом автор сверился с трекером: #39650 («Slack importer: thread-to-topic conversion and import-robustness bugs») открыт 30 июня 2026 автором amanagr — систематический аудит import path, порядка дюжины пунктов, triaged. Оба его открытия уже в списке, описаны точнее, чем он бы сформулировал сам. По его оценке — опоздание примерно на две недели.
Для соло-workflow с агентами в цикле это не провал, а калибровка: проверенная зона может быть уже «зачищена», а ценность — в переносе классов багов на соседний код.
Этика заявки: агент ускоряет поиск, но не решает, чей PR не трогать
Когда агентские инструменты уже помогли пройти кодовую базу, соблазн «закрыть всё своим PR» растёт — и именно здесь человек отделяет охоту от гонки чужой работы.
Сопровождающий PieterCK (владелец импортёров) уже вёл открытый PR #39757 «slack_importer: Fix Slack thread conversion bugs» — в diff оба thread-бага чинились с контекстом мейнтейнера.
По правилам челленджа «Smash Bugs, Respectfully» автор отступил от правок багов тредов — не стал гонять чужой PR по пунктам аудита. Независимое переоткрытие двух пунктов он трактует как проверку метода, а не потерю времени.
PR #39757 смержен 27 июля 2026; в self-review checklist есть ссылка на AI use policy Zulip. Фикс закрывает пункт 1 в #39650 (cross-chunk thread fragmentation).
Латентный двойник в Teams: перенос паттерна, который агент не увидит сам
После Slack-аудита автор вручную перенёс классы багов на однотипные импортёры — шаг, который агентские инструменты не заменяют без инженерной гипотезы. Mattermost на этих классах чист. Microsoft Teams — новейший в семействе — дал баг в get_batched_export_message_data: генератор отдаёт список и затем вызывает .clear() на том же объекте для следующего батча.
list(batched(range(12), 5))
# expected: [[0..4], [5..9], [10, 11]]
# actual: [[10, 11], [10, 11], [10, 11]]
Каждый отдаваемый батч — один общий список; 10 из 12 сообщений исчезают, без исключений. Баг латентный: текущий вызывающий код Zulip потребляет батч до advance, но контракт сломан для потребителя, который удерживает батчи (например list(...)).
Никто не репортил — стало PR #39814 (автор P0rt, 24 июля 2026, статус open, label needs discussion). Регрессионный тест материализует генератор; на старом коде — AssertionError: 24 != 29.
Спор с Seer: ИИ предложил фикс, человек выбрал политику обработки сбоев
На другом треке челленджа автор подключил краш к Sentry и спросил Seer (ИИ-дебаггер Sentry) о корневой причине. Seer быстро попал в диагноз, процитировав «poisoned message» из frame locals.
Предложенный патч в одну строку: float(message.get("ts", 0)) — запасная временная метка, которую автор уже отклонил в своём PR. По тексту поста патч закрывает missing ts, оставляет ValueError, пропускает "NaN" в сортировку и ставит реальным сообщениям date_sent в 1970 — «один режим сбоя из трёх, плюс выдуманная хронология».
Авторская защита в PR #39813 требует math.isfinite, не только успешный parse — чтобы поймать тихий режим NaN: float("NaN") не падает, NaN ломает total ordering Timsort, sorted() даёт inconsistent order без crash. PR от 24 июля 2026, open; мейнтейнер laurynmm 27 июля просит обсудить в backend-канале: пропуск vs синтез запасной метки.
В #39650 high-impact пункт про unguarded timestamp sort key был confirmed; в diff #39757 (Ctrl+F) не найден — автор взял его в #39813. Дисциплина: regression test first → rollback to upstream/main → watch fail → restore fix; Slack module 56/56, Teams 10/10, lint/mypy clean.
Seer ускорил диагноз — но политика обработки сбоев (пропуск с предупреждением vs запасная метка в 1970) остаётся инженерным решением, которое агент не должен подменять патчем в одну строку.
AI policy Zulip и урок для соло-контрибьютора
У Zulip есть явная AI use policy для контрибьюций, и оба открытых PR автора (#39813, #39814) в checklist отмечают её соблюдение. По публичным формулировкам через CONTRIBUTING и PR-шаблон: ИИ-инструменты разрешены, но контрибьютор несёт полную ответственность — понимать, тестировать и объяснять изменения; PR, похожие на непроверенный AI-output, могут закрываться без review.
Уроки из поста, релевантные vibe coding в одиночку:
- проверенная зона уже закрыта → охотиться в свежем коде;
- повторное открытие = калибровка, не стыд;
- «заведено в трекере» ≠ «исправлено»;
- читать открытые PR перед гонкой;
- краш — «удачный режим сбоя» по сравнению с тихой порчей данных.
Стратегия «агенты на механике, человек на политике и этике» здесь не лозунг, а документальный след в GitHub: #39650, #39757, #39813, #39814.
Источники
- I Found Two Bugs in Zulip. The Maintainers Had Filed Both Two Weeks Earlier. — p0rt, Dev.to, 2026-08-07
- Issue #39650 — Slack importer: thread-to-topic conversion and import-robustness bugs — Zulip GitHub
- PR #39757 — slack_importer: Fix Slack thread conversion bugs — PieterCK, merged 2026-07-27
- PR #39813 — slack_importer: Skip messages with invalid timestamps — P0rt, open
- PR #39814 — microsoft_teams: Don't reuse yielded batch lists in message batching — P0rt, open
- Zulip AI use policy and guidelines — Zulip documentation
- How to Respectfully Contribute to Open Source — контекст «Smash Bugs, Respectfully»