fix(webhooks): await the Telegram provider handler so the dedup claim is released - #1429
lilyshen0722 wants to merge 1 commit into
Conversation
… is released #1422 merged at e794b1f with this outstanding. The last exit of the POST handler's try block was the only un-awaited return of 14: return events(req, res); `events` is async and awaits a Mongo write (telegramProvider.ts:111, :126), and `return promise` inside a try does not reach that try's catch — only `return await promise` does. express is 4.21.2 here with no express-async-errors and no asyncHandler wrapper, so a rejection from the provider escapes the route entirely: the forget-on-error release never runs, no response is sent, and Telegram's redelivery is then acked 200 as a duplicate and dropped permanently. That is the exact swallowed-drop the release contract exists to prevent — the comment on the catch says so directly ("the claim must not survive to swallow that retry") — and it is new behaviour, because before the dedup claim existed the redelivery simply reprocessed. The liveRelay branch immediately above already awaits its bridge call, so this was the one escaping path. The 15 shipped tests cannot see it: every test reaching this line stubs `events` synchronously, while the real one is async. The added test stubs it async and rejecting. Without the fix it times out — no response is ever sent, which is the defect's own signature; with the fix, 16/16 pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5ad592e to
39c9ad9
Compare
|
Rebased onto Re-verified rather than assumed, six hours on:
Merge-order note: #1297 ( |
|
Superseded by #1449, which carries @sprint-impl's Their commit was stranded: it sat on #1422's branch, and #1422 is merged and squash-merged, so a PR from there would have replayed all 276 lines and conflicted. #1449 is that commit cherry-picked onto current main — 60 insertions, applies clean. |
Follow-up to #1422, which merged at
e794b1fcwith this outstanding. Verified present onorigin/mainatbackend/routes/webhooks/telegram.ts:465.The defect
The last exit of the POST handler's
tryblock is the only un-awaited return of the 14 in it:Four links, each measured rather than reasoned:
eventsis async and can reject —telegramProvider.ts:111declares itasync,:126awaitsIntegration.findByIdAndUpdate.return promiseinside atrydoes not reach thatcatch— onlyreturn await promisedoes. Verified in isolation on node 22.express-async-errors, noasyncHandlerwrapper anywhere inbackend/.claimDeliveryreturns'duplicate', and the update is acked 200 and dropped permanently.That is precisely what the release contract exists to prevent — the comment on the
catchsays it outright: "the claim must not survive to swallow that retry." And it is new behaviour: before the dedup claim existed, the redelivery just reprocessed. TheliveRelaybranch immediately above already awaits its bridge call, so this was the single escaping path.Why 15/15 was green
Every test that reaches this line stubs the handler synchronously:
while the real one is
async. The mock differs from production in exactly the dimension the bug lives in, so the suite could not have caught it at any coverage level.The added test
Stubs
eventsasync and rejecting, then asserts the release fired and a 500 went out. The differential:awaitawaitRun on node 22. I reverted the one-word fix and re-ran to confirm the test actually discriminates, rather than trusting that a new test which passes is testing anything.
Not fixed here
Two things I raised on #1422 that are deliberately out of scope for a one-word fix, both worth their own change:
updateId !== undefined && updateId !== nullguards are unpinned — deleting either one survives the suite. Without them an update with noupdate_idclaims the literal key'undefined', and every subsequent one is acked and dropped. Ten of the fifteen shipped tests post exactly that shape and all stay green.TELEGRAM_SECRET_TOKENandTELEGRAM_WEBHOOK_ALLOW_UNVERIFIEDappear nowhere underk8s/, whileTELEGRAM_BOT_TOKENis wired (backend-deployment.yaml:356). The bridge is provisioned, so deploying as-is fail-closes every inbound update. Chart change andsetWebhook(secret_token=…)need to land before this reaches the cluster.🤖 Generated with Claude Code