Перейти к содержимому

Функция Slack-дайджеста, которая отслеживает то, что я пропустил

Когда я полностью погружен в другую задачу, Slack перестает быть тем, что я проверяю. Я настроил так, чтобы дайджест запускался автоматически — и ожидающие меня сообщения всё равно всплывают.

Машинный перевод
Снятый сверху монитор, на котором в Obsidian открыта ежедневная заметка с разделом «Slack digest», содержащая как отмеченные, так и неотмеченные Markdown-флажки, на деревянном столе с механической клавиатурой и керамической кружкой.
Изображение сгенерировано ИИ

Я закрываю 3+ позиции в стартапе. Цена переключения между режимами проявляется в Slack. В течение обычного дня есть решения и суждения, которые меняются только тогда, когда я смотрю — а когда я глубоко погружен в другую задачу, я не смотрю часами.

У меня был скрипт для Slack-дайджеста, который я собрал в начале этого года. Он сканировал каналы, обобщал шум, давал мне понятную картину — полезно, но я должен был не забывать его запускать. В те утра, когда я уже был чем-то поглощён, я забывал — а именно тогда он был мне нужен больше всего. Поэтому я сделал так, чтобы он запускался сам в начале каждого рабочего дня и появлялся в единственном месте, которое я не могу игнорировать.

Чего я на самом деле хотел

Дайджест должен приходить без моего запроса. Он должен появляться там, где я не смогу его игнорировать. А личные сообщения и упоминания должны появляться буквально сверху, а ниже — отборный дайджест каналов. Личные сообщения и упоминания — это сообщения, которые я теряю; остальное — шум, который LLM может сжать.

Куда его поместить — это было первое решение. DM в Slack сам по себе был бы надёжным, но находился бы в том же шуме, в котором я и так теряю вещи. Push-уведомление было бы невозможно пропустить, но оно эфемерно. Ежедневная заметка в Obsidian решала обе проблемы: я и так заполняю её вручную тем, что делаю в течение дня, поэтому открываю её каждый рабочий день. Добавлять дайджест туда означает, что я вижу его каждый рабочий день по умолчанию.

Проработку требований я осуществил с помощью /deep-interview — навык сократического интервью, который заключается в том, чтобы задавать целенаправленные вопросы, пока спецификация не станет конкретной. Через шесть раундов неоднозначность составила 5%. Именно интервью и сыграло полезную роль. Оно заставило меня быть честным в отношении случаев, от которых я отмахивался. Самым важным было временное окно для «непрочитанного».

Моя исходная рамка была «за последние 24 часа». Контрарный раунд спросил меня, что будет по понедельникам. Если я заканчиваю в пятницу после обеда, а дайджест запускается в понедельник в 13:00, «за последние 24 часа» сканирует только воскресенье. Я пропускаю пятницу. После недели отпуска я пропускаю целую неделю. Ответ был «с момента последнего дайджеста». Одна временная метка в файле состояния. Одно правило, которое одновременно обрабатывает выходные, отпуска и бэктлог после совещаний.

Случай с границами рабочего дня требовал отдельного внимания. Я работаю по канадскому времени, которое приходится примерно на период с позднего дня до раннего утра по моему местному времени. Моё утро — это примерно полдень по местному времени. Наивное правило «запускать при первом запуске в день» приводило бы к запуску посреди смены, где-то в полночь по моему времени, когда я ещё работаю. Интервью возразило против барьера с полуднем, доказывая, что «с момента последнего дайджеста» уже гарантирует, что ничего не пропущено, а барьер — это просто лишняя сложность. Как только я привёл в пример канадское время, он изменил свою позицию. Барьер остался: он нужен не для того, чтобы ловить пропущенные сообщения, а для того, чтобы дажджист появлялся в начале моего рабочего дня, а не посередине.

Что я создал

Хук SessionStart запускается каждый раз, когда я запускаю Claude Code. Он проверяет три условия: будний день, время после полудня по местному времени и файл состояния, фиксирующий, запускался ли я уже сегодня. Если все три условия выполняются, он запускает отдельный фоновый раннер и завершает работу. Хук должен был быть быстрым — всё, что замедлило бы каждый запуск сессии, заставило бы меня его отключить.

Фоновый раннер вызывает claude -p /slack-morning-digest <window-start>запуск скрипта, который извлекает DM-сообщения и упоминания буквально со Slack MCP-сервера и прогоняет логику дайджеста каналов по всему остальному. Результат добавляется к сегодняшней ежедневной заметке в Obsidian. Если заметка ещё не существует, раннер её создаёт.

Я прогнал план через Planner, Architect и Critic ещё до того, как был написан какой-либо код. Architect обнаружил два реальных бага корректности в конфигурации хука: неправильное вложение в settings.json и флажок CLI, которого на самом деле не существует. Critic возразил против третьего замечания и предложил проверочный спайк, чтобы подтвердить два архитектурных предположения, прежде чем я закреплюсь в дизайне. Оба прошли. Спайк обошёлся дешевле, чем обнаружение любого из этих сбоев в середине реализации.

Что сломалось в рантайме

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

Первый был spawnSync ... claude.cmd EINVAL. Node 24 отклоняет spawnSync файлов .cmd и .bat, если не передано shell: true. Команда claude в Windows реализована как .cmdобёртка вокруг настоящей .exe. Исправление заключалось в том, чтобы нацелить раннер прямо на настоящий .exe вместо оболочки — и EINVAL прекратился. Субботний тест сработал триггер, но не запустил раннер от начала до конца.

Вторым багом было пятиминутное зависание. С исправленным EINVAL claude.exe -p запускался, но никогда не завершался. Причина заключалась в том, что в headless-режиме каждый вызов MCP-инструмента сопровождается запросом разрешения, на который никто не может ответить. Процесс ждёт ввода, который никогда не поступит. Исправлением стал --permission-mode bypassPermissions, с --disallowedTools mcp__slack__conversations_add_message в качестве дополнительной гарантии «только для чтения». Даже если разрешения обойдены, раннер структурно неспособен писать в Slack.

Третья проблема всплыла как побочное наблюдение. Триггер, который в субботу срабатывал за несколько миллисекунд, в производственной среде тянулся почти до секунды. PowerShell Start-Process — вот что Planner вписал в раннер, — на Windows это оправдано, но задержки холодного старта можно было избежать. Агент обнаружил регрессию, пока исправлял EINVAL, и заменил его на нативный spawn(detached: true, stdio: 'ignore', windowsHide: true) от Node + child.unref(). Триггер вернулся к нескольким миллисекундам.

И этот баг заметил только я. Первое успешное срабатывание в понедельник отображалось как «за последние 24 часа», потому что lastSuccessfulFire он всё ещё был null. Не было ни одного предыдущего успешного запуска, к которому можно было бы привязать окно. Поэтому дайджест охватил только воскресенье, а не с пятницы по воскресенье. Исправлением стал «холодный старт» с учётом дня недели: понедельник получает 72 часа, со вторника по пятницу — 24 часа. После первого успешного срабатывания эстафету принимает lastSuccessfulFire, и ветка холодного запуска больше никогда не запускается. Баг был в предельном случае, который срабатывает лишь один раз за всю жизнь системы. Также он сделал бы первый понедельник бесполезным.

Что система делает сейчас

При первом за неделю запуске после полудня по местному времени срабатывает триггер. Через несколько минут моя ежедневная заметка в Obsidian получает раздел с дословными DM-ами и упоминаниями вверху и курируемым итогом каналов ниже. Я читаю его, хочу я того или нет, потому что ежедневная заметка — это уже то место, где я записываю, чем занимаюсь в течение дня. Усилия, связанные с её открытием, — нулевые.

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

Сама сборка от начала до конца осуществлялась с помощью ИИ. Я направил прохождение /deep-interview для спецификации, затем прохождение Planner / Architect / Critic по дизайну. Claude Code написал хук и раннер под этим руководством. Все эти агенты живут в Oh My ClaudeCode — плагине для Claude Code, который я ежедневно использую для большей части нетривиальной работы.

Интервью и прогоны Architect/Critic выявили ошибки корректности ещё до того, как был написан какой-либо код. Продакшн выявил то, что эти проверки не затронули. Мне было бы интересно услышать, как другие используют Claude Code или любой ИИ-инструмент, чтобы выявлять то, что иначе осталось бы незамеченным в их собственной работе.

Включите комментарии, приняв куки.

Необходимые куки работают всегда. Куки для статистики и комментариев используются только с вашего согласия. Подробнее