#16995: fix(telegram): record update ID before processing to prevent crash replay
channel: telegram
stale
size: S
Cluster:
Telegram Command Fixes
## Summary
- **Bug:** `recordUpdateId(ctx)` was called after `await next()` in the logging middleware (`src/telegram/bot.ts:215-228`). If the process crashed during `next()` (while processing the message), the update offset was never persisted. On restart, Telegram would redeliver the same update, causing duplicate message processing.
- **Fix:** Move `recordUpdateId(ctx)` to before `await next()` so the offset is recorded before the handler runs. `recordUpdateId` has no dependency on the result of `next()` — it only reads `ctx.update.update_id` and persists it via `onUpdateId`.
- Added a test that verifies `onUpdateId` is called before `next()` completes.
## Test plan
- [x] New test `bot.create-telegram-bot.records-update-id-before-processing.test.ts` asserts `onUpdateId` fires before `next()`
- [x] Existing dedupe and update-offset-store tests pass unchanged
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Moved `recordUpdateId(ctx)` before `await next()` in the Telegram logging middleware to prevent duplicate message processing after crashes. Previously, if the process crashed during message handling, the update offset wasn't persisted, causing Telegram to redeliver the same update on restart. The fix is correct because `recordUpdateId` only reads `ctx.update.update_id` and has no dependency on the result of `next()`.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge - it fixes a critical duplicate message bug with a minimal, well-tested change
- The fix is a simple two-line swap with clear logic: recording the update ID before processing ensures it's persisted even if the process crashes. The recordUpdateId function has no dependencies on the result of next(), making the reordering safe. The new test directly validates the execution order, and existing tests remain unchanged, confirming no regressions
- No files require special attention
<sub>Last reviewed commit: 6a72472</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#16105: fix: handle message_reaction updates in group polling mode
by claw-sylphx · 2026-02-14
81.8%
#11347: fix: scope Telegram update offset to bot token
by anooprdawar · 2026-02-07
81.4%
#18115: fix: prevent voice message loss during concurrent update processing
by AlekseyRodkin · 2026-02-16
79.7%
#3186: fix(telegram): sanitize update offset + lock polling
by daxiong888 · 2026-01-28
79.6%
#8166: fix(telegram): lifecycle fixes for duplicate messages and auto-reco...
by cheenu1092-oss · 2026-02-03
78.0%
#16548: fix(telegram): enhance chat_id validation and diagnostics
by tanujbhaud · 2026-02-14
77.8%
#19213: Telegram: preserve DM topic thread in direct replies
by Kemalau · 2026-02-17
77.7%
#23238: fix(telegram): account named "default" silently breaks inbound polling
by anillBhoi · 2026-02-22
77.2%
#14443: fix(telegram): skip General topic thread ID for all chat types (#14...
by lailoo · 2026-02-12
77.2%
#19800: fix(telegram): add INFO-level logging at inbound message drop paths
by katalabut · 2026-02-18
77.1%