fix: make new-post detection reliable and guard the manual trigger - #7
Merged
Conversation
The cron loop could re-send the same audio, or miss a new one, and the HTTP entrypoint let anyone post to the chat. Four related fixes: - KV read failures no longer default to 0. getCountFromKV swallowed errors and returned 0, which the handler read as "nothing has ever been sent" and used to re-send the latest audio every 10 minutes until KV recovered. Reads and writes now propagate their errors. - KV write failures are no longer silent. updateKVCount returned a Response with status 500 that the handler logged and ignored, so the next tick re-sent the same post. setLastSentAt throws instead. - Detection compares publish times rather than post counts. A deleted post plus a new one left the category count unchanged, so the new reminder was never sent. The worker now stores the publish time of the last post it sent under fr:last-sent-at and sends only when the latest post is strictly newer, which also keeps a deletion from making an older post look new. On an empty namespace it seeds the timestamp without sending. - The manual trigger requires POST and a TRIGGER_SECRET bearer token. It previously sent to the chat on any request to any path, and now also records the timestamp so a manual send is not repeated by the next cron run. Also: errors are reported once instead of twice (send() and its callers both notified), audio larger than Telegram's 50 MB bot limit is rejected before it is buffered into memory, the duplicated sendMessage bodies and `as any` error casts are collapsed into shared helpers, and the unused cleanMp3Filename, sendTelegramMessage and getCountFromWordpress are removed. Adds a GitHub Actions workflow running typecheck and tests, an npm run typecheck script, and tests covering the send/skip/seed decisions, the trigger's auth, the KV round-trip and the date parsing. tsc --noEmit did not pass before this change; moduleResolution is now "bundler" so the vitest config's types resolve, and tests/env.d.ts types the test-only env bindings. Note for deploy: the KV key changed from `fr` (a count) to `fr:last-sent-at` (a timestamp). The first run after deploy seeds the new key without sending, so no reminder is re-posted; the old `fr` key is unused and can be deleted. Set the new TRIGGER_SECRET with `wrangler secret put TRIGGER_SECRET` — until it is set the manual trigger returns 503 and the cron path is unaffected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JN4sR41DwRmboRfngdiTLE
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
mhmic-telegram-cron | 8a9fba0 | Aug 23 2026, 09:16 PM |
omarmosid
approved these changes
Aug 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The cron loop could re-send the same audio, or miss a new one, and the HTTP entrypoint let anyone post to the chat. This fixes those four bugs, adds tests around them, and adds CI.
Bugs fixed
1. KV read failures caused repeated sends.
getCountFromKVcaught its own errors and returned0. The handler read that as "nothing has ever been sent", sowordpressCount > 0was true and the latest audio was re-posted on every 10-minute tick until KV recovered. Reads now propagate their errors, and the handler skips the cycle instead of sending.2. KV write failures were silent.
updateKVCountreturned aResponsewith status 500 rather than throwing; the handler loggedwasSuccessful = 'fail'and returned normally, so the next tick re-sent the same post.setLastSentAtthrows, and the failure reaches the notifications chat.3. Post counts were the wrong signal. Comparing the category's post count against KV missed the case where a post is deleted and another added — the count is unchanged, so the new reminder was never sent. The worker now stores the publish time of the last post it sent (
fr:last-sent-at) and sends only when the latest post is strictly newer. Comparing times also means a deletion cannot make an older post look new, which an id comparison would have allowed.getCountFromWordpressis no longer needed, which also drops one API call per tick.4. The manual trigger was unauthenticated.
fetchHandlersent to the Telegram chat on any request, any method, any path — anyone who learned the workers.dev URL could spam the chat and pull a full audio download through the worker. It now requiresPOSTand aTRIGGER_SECRETbearer token, compared without leaking where it diverges, and fails closed (503) when the secret is unset. It also records the timestamp on success, so a manual send is not repeated by the next cron run.Also in this change
send()notified and rethrew, and both callers notified again. Notification now happens once, at the top-level handlers.sendMessagebodies and threeerrorData as anycasts collapse into shared helpers with a typed error shape.cleanMp3FilenameandsendTelegramMessage(unused insrc/), plusgetCountFromWordpress.README.mdnow covers secrets, deploy, local dev and the trigger;.env.examplesays these are Wrangler secrets rather than implying dotenv; thesrc/index.tscomment no longer referenceswrangler.toml.Tests and CI
38 tests pass (was 11). New coverage: the send/skip/seed decisions in
scheduledHandlerincluding the KV-failure and failed-send paths, the trigger's auth and method checks, the KV round-trip and corrupt-value handling, and the date parsing..github/workflows/ci.ymlruns typecheck and tests on pushes tomainand on PRs, plus annpm run typecheckscript.tsc --noEmitdid not pass before this change:moduleResolutionis now"bundler"so the vitest config's types resolve, andtests/env.d.tstypes the test-onlyenvbindings.Deploy notes
fr(a count) tofr:last-sent-at(a timestamp). The first run after deploy finds the new key empty and seeds it without sending, so no reminder is re-posted. The oldfrkey is unused and can be deleted.wrangler secret put TRIGGER_SECRET. Until it is set, the manual trigger returns 503; the cron path is unaffected.What I did not change
music-metadataduration/performer, so that tradeoff is yours to make.Generated by Claude Code