Перейти до вмісту

Хук Slack-дайджесту, що ловить пропущене мною

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

Знятий згори монітор, на якому в Obsidian відкрита щоденна нотатка з розділом «Slack digest» зі змішаними позначеними й непозначеними Markdown-чекбоксами, на дерев'яному столі з механічною клавіатурою та керамічним кухлем.
Зображення згенеровано ШІ

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

У мене був скіл Slack-дайджесту, який я зібрав раніше цього року. Він сканував канали, підсумовував шум, давав мені читабельну картину - корисно, але я мусив пам'ятати його запустити. Тими ранками, коли я вже був у щось занурений, я забував - а саме тоді він був мені найбільше потрібен. Тож я зробив так, щоб він запускався сам на початку кожного робочого дня й опинявся в єдиному місці, яке я не можу ігнорувати.

Чого я насправді хотів

Дайджест має приходити без мого запиту. Він має приземлятися десь, де я не можу його ігнорувати. А DM-и та згадки мають з'являтися дослівно зверху, з курованим дайджестом каналів нижче. DM-и та згадки - це повідомлення, які я гублю; решта - шум, який 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 чи будь-який ШІ-інструмент, щоб ловити те, що інакше нагромаджувалося б непоміченим у їхньому власному дні.

Увімкніть коментарі, прийнявши куки.

Необхідні куки працюють завжди. Куки для статистики та коментарів використовуються лише з вашої згоди. Докладніше