Skip to content

fix(agenda): stop dropping 15 s+ odroid renders and stop blaming the date range (HYP-1671) - #798

Merged
alex-mextner merged 5 commits into
mainfrom
fix/agenda-render-wait-budget
Oct 12, 2026
Merged

alex-mextner merged 5 commits into
mainfrom
fix/agenda-render-wait-budget

Conversation

@alex-mextner

Copy link
Copy Markdown
Owner

What this fixes

On 2026-10-12 at 18:00:31 UTC the owner ran /today or /tomorrow (both log the events_create feature, at 18:00:29) and got «Agenda image could not be generated. Choose a shorter date range.» for a single day.

Production log (odroidn2, image c7f2ec0c):

18:00:31.643  Enqueuing render job          type=daily-agenda
18:00:46.716  Render failed                 Job wait render timed out before finishing, no finish notification arrived after 15000ms (id=539)
18:00:48.753  Image rendered                renderTimeMs=15183 height=1015

The worker finished the image 2 s after RenderService.renderDirect stopped waiting (src/services/image/render-service.ts:5,23, fixed 15 s waitUntilFinished). The image was thrown away, and the command blamed the date range because every error it did not recognise got the «shorter date range» text (src/bot/commands/today.ts:126 and the same line in tomorrow/week/month).

Why renders are this slow now: every render in the current container's log takes 5.9–15.2 s (9924, 8155, 8113, 5944, 12403, 15183 ms). The same daily template renders in 0.8–1.8 s on a warm page inside the same container (one-off probe, 2026-10-12). The difference comes from the host: the bot's cgroup had 501 MB in swap (memory.swap.current, zram plus a microSD swapfile; the bot's Bun process alone had 444 MB swapped out, with 985k major faults), so an idle Chromium and Bun heap have to be paged back in on every render. The 15 s budget was set on the DigitalOcean droplet.

Changes

  1. fix(image): wait 45 s for a render, about 3× the slowest measured production render. The wait is still bounded.
  2. fix(agenda): when a render fails for any reason other than image size, /today, /tomorrow, /week and /month send the localized notice the callbacks already use, «⚠️ Image generation failed. Use text version above.» / «⚠️ Не удалось сгенерировать изображение. Смотри текстовую версию выше.». The text agenda always goes out before the image. Image-size errors keep their own message, which does mention the range.
  3. test(image): the regression test asserts the exact 45 s wait.

Tests (red → green)

  • test/services/image/render-service.test.ts: a worker that finishes at 15 183 ms must still be delivered. Red on main with the exact production error Job wait render timed out … after 15000ms (id=539); green after change 1.
  • test/bot/commands/{today,tomorrow,week,month}.test.ts: a non-size render failure (including the exact production timeout error) gets the honest localized notice, never the range advice. Red on main (received «…Choose a shorter date range.»); green after change 2.
  • Full suite on a Runpod pod (AGENTS.md): 6930 pass, 16 skip, 0 fail. tsc --noEmit exit 0. bun run lint exit 0. At the final head: the 5 touched files gave 48 pass, tsc 0, lint 0.

Review

AI review ran on every commit. One recurring finding was not acted on, for this reason: the AI agent checks its 90 s turn deadline only between rounds, and no tool is cut by it, so a render that starts late can end a turn past 90 s. The 15 s wait already allowed this; with 45 s the overrun can be longer. The turn's failure notice is only produced after the tool returns, so a photo can never arrive after a timeout notice. Discarding an image the worker already finished, which is what this incident was, is worse. The tradeoff is written down at the constant.

Refs HYP-1671

…enders

On the ODROID-N2+ every production render takes 6-15 s (most of the bot's memory sits in swap between renders). On 2026-10-12 18:00 UTC a daily agenda finished in 15 183 ms, two seconds after renderDirect stopped waiting at 15 000 ms, so the finished image was discarded. 45 s is 3x the slowest measured render and stays inside the 90 s AI turn in the common case.

Refs HYP-1671 (task:HYP-1671)
/today, /tomorrow, /week and /month answered every render failure that was not an image-size error with 'Choose a shorter date range.', so the 2026-10-12 render timeout on a single day told the owner to shorten a one-day range. They now send the localized 'Image generation failed. Use text version above.' notice the callbacks already use (the text agenda is always sent first); size errors keep their own message.

Refs HYP-1671 (task:HYP-1671)
The regression test now asserts the exact wait, so a lowered or raised budget fails. The comment states plainly that a render, like every tool, is not cut by the AI turn's between-rounds 90 s deadline.

Refs HYP-1671 (task:HYP-1671)
…ies, not as-never

type-bans blocks added 'as never' everywhere; the new /today and /month cases now go through one factory per file that keeps the test-only cast in one place (CLAUDE.md) and takes a real ImageRenderer.

Refs HYP-1671 (task:HYP-1671)
Refs HYP-1671 (task:HYP-1671)
@alex-mextner
alex-mextner merged commit 472bd4b into main Oct 12, 2026
10 checks passed
@alex-mextner
alex-mextner deleted the fix/agenda-render-wait-budget branch October 12, 2026 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant