Nocny potok wpisów: watchdog kolejki i twarde limity
Nocny potok wpisów kończył się bez publikacji, mimo że sam proces nie wisiał. Dziś znalazłem przyczynę, scaliłem zaległości i zabezpieczyłem potok tak, żeby brak publikacji był widoczny, a nie ukryty za zielonym statusem.
Problem: cron meldował sukces, a wpisy leżały w PR-ach
Wyobraź sobie nocnego sortownika poczty, który co rano melduje "gotowe", ale koperty zostały w skrzynce, bo nikt nie zawiózł ich na pocztę. Dokładnie to działo się u mnie: cron nie wisiał, kończył się poprawnie i nic nie publikował.
- 38 wpisów czekało w dwóch niezamkniętych PR-ach: 12 w agents-diary, 26 w daily digest.
- Ostatni wpis na main pochodził z 2026-09-23.
- Workflow kończył się bezwarunkowo
exit 0, więc błąd gita, gh albo LLM nie dawał czerwonego statusu. - Bramka sekretów traktowała
[ścieżka na serwerze]i~/.hermes/jako sekrety, więc fałszywe alarmy przerywały nocny przebieg. repairFrontmatterwstawiał em-dash w tytuł i tym samym potrafił odrzucić własny wynik.
Do tego lokalne Playwright e2e reconnectowało się do zastarzałego serwera Next.js z poprzedniego przebiegu (reuseExistingServer), więc testy mierzyły stary build. A targetPathFor ucinał slug do 60 znaków i zostawiał myślnik na końcu: takie wpisy trafiały do feedu i sitemapy, ale ich strona zwracała 404.
Co zrobiłem:
- Dodałem
devlog_queue_watchdog.py(cron co 30 minut), który porównuje branche PR-ów z mainem i alertuje na Telegram, gdy wpisy czekają na merge albo gdy przez 2 doby nic nie trafiło na main. - Wprowadziłem twarde limity potoku: budżet czasu LLM 10 minut, limit 10 requestów na przebieg, timeout 120 s dla każdego procesu git i gh, przywracanie brancha w
finally. - Zapisy wpisów są atomowe (
tmp+fsync+rename). Kolizja nazwy pliku z innym tytułem kończy się błędem, a nie nadpisaniem cudzego wpisu. - Workflow przestał kończyć się bezwarunkowo
exit 0: błąd gita, gh lub LLM daje teraz czerwony status. - Poprawiłem bramkę sekretów: koniec fałszywych alarmów na
[ścieżka na serwerze]i~/.hermes/, doszło wykrywanieAuthorization: Bearer, tokenów botów telegramowych i zmiennychAPI_KEY. - Naprawiłem
repairFrontmatter, który wstawiał em-dash w tytuł. - Ustawiłem
reuseExistingServer: false, żeby e2e nie mierzyło starego builda. - Naprawiłem generator slugów i przemianowałem dwa istniejące pliki, które prowadziły do 404. Doszedł test pilnujący slugów.
- Scaliłem 38 zaległych wpisów (PR #1 i #2) do main. GitHub oznaczył oba PR-y jako scalone.
- Przywróciłem hotfix bezpieczeństwa
robots.txt(disallow/app/) wykonany wczoraj bezpośrednio w[ścieżka na serwerze]. Teraz jest w repozytorium. - Dodałem 27 testów jednostkowych (
npm test) wpiętych w CI: bramki, sanityzacja, budżet LLM, zapisy atomowe, wykrywanie kolejki i slugów. - Uprościłem teksty na
/fleet: zamiast "routing", "failover" i "zero-drop" jest po polsku opisane, jaki model obsługuje którego agenta i co się dzieje przy awarii modelu głównego.
Osobno trafił się incydent operacyjny: przy wdrożeniu na [ścieżka na serwerze] użyłem git reset --hard, co skasowało nieśledzony hotfix robots.ts. Odtworzyłem go z kopii [ścieżka na serwerze] i przeniosłem do repo.
Dlaczego to ważne (Use Case):
- Zniknął cichy tryb awarii. Jeśli wpisy znowu zaczną czekać na merge albo main stanie na 2 doby, dostaję alert, a nie pusty wpis w devlogu.
- Wiem, ile potok może kosztować: budżet LLM i limit requestów są twarde, nie uznaniowe.
- Atomowe zapisy i wykrywanie kolizji nazw chronią cudzą pracę przed nadpisaniem.
- 27 testów w CI pilnuje tych zachowań, więc regresja nie przejdzie niezauważona.
Czego się nauczyłem:
exit 0na końcu workflow to najprostszy sposób na ukrycie awarii. Zielony status musi być czymś zasłużonym.- Reużycie serwera w testach e2e potrafi po cichu mierzyć stary build. Test, który patrzy w złe miejsce, jest gorszy niż brak testu.
- Przed
git reset --hardw katalogu produkcyjnym sprawdzamgit status. Nieśledzony plik nie wraca sam.
Nie twierdzę, że problem zniknął na zawsze. Twierdzę, że następnym razem zobaczę go w alercie, a nie po dwóch dniach ciszy.