Skip to content

fix(webhooks): await the buffer handler so a rejection releases the claim - #1449

Closed
lilyshen0722 wants to merge 1 commit into
mainfrom
fix/telegram-await-buffer-handler
Closed

lilyshen0722 wants to merge 1 commit into
mainfrom
fix/telegram-await-buffer-handler

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

Carries @sprint-impl's commit 50629076 — cherry-picked, authorship preserved. Opening it here because it could not merge from where it was, which is the thing worth flagging before the gate itself.

Why this PR exists

50629076 was pushed onto fix/telegram-webhook-dedup-ack, the branch behind #1422. #1422 is merged and closed, and pushing to a merged PR's branch neither reopens it nor creates a new one — it only bumps the PR's updatedAt, which is what made it look updated at that sha.

Worse, #1422 was squash-merged, so e794b1fc is not an ancestor of main. Their merge-base is 94012454, from this morning. A PR opened directly from that branch would have shown 276 insertions across 3 files — replaying all of #1422 on top of the squashed copy already on main — and git merge-tree reports it as conflicting.

Cherry-picked onto current main it is 60 insertions across 3 files, applies clean, and is exactly the intended delta.

Gate — PASS

The fix, and the reason it is await and not bare return:

// await, not return: a rejected promise returned from inside `try` escapes
// this catch (express 4 won't catch it either), and the claim above would
// survive to swallow Telegram's redelivery.
return await events(req, res);

17/17 green on node 22. Mutation-tested rather than trusted, each anchor asserted to have moved:

Mutation Result
return awaitreturn (revert the fix) killed
remove the claim-side updateId !== undefined && !== null guard killed by the new "processes every update that carries no update_id"
remove the release-side updateId guard survives

One correction to the summary

"Both surviving mutants pinned" is half right. The claim-side guard — the load-bearing one, where an un-id'd update would otherwise claim the literal key 'undefined' and every later one be acked and dropped — is now genuinely pinned. The release-side guard is still unpinned.

It is benign, and for a principled reason: because the claim side never claims 'undefined', releasing it is a no-op, so removing the guard produces no behavioural difference for a test to catch. It is still pinnable — the observable is the wasted call, expect(WebhookDelivery.deleteOne).not.toHaveBeenCalled() on the no-id throw path. Worth one line if anyone touches this again; not worth holding the PR.

The key-scope note landed on the model as described, and it names the right condition — update_id is sequential per bot, so a second bot on this route would collide id spaces.

Still outstanding

The deploy prerequisite, which #1444 appears to be taking: TELEGRAM_SECRET_TOKEN is still absent from k8s/, so verification fail-closes every inbound update until the chart lands and setWebhook(secret_token=…) runs.

Supersedes #1429, which I opened for the same one-word fix before this work surfaced; I am closing it — this version pins strictly more.

🤖 Generated with Claude Code

…laim

Sprint-review gate catch: `return events(req, res)` un-awaited inside the
try meant an async rejection escaped the catch — the dedup claim survived,
and Telegram's redelivery was acked as a duplicate, dropping the update
permanently. Now `return await`, with a comment stating why.

Also pins the gate's two surviving mutants:
- async-rejecting buffer handler → 500 + claim released (the old sync mock
  could never exercise the escape)
- updates with no update_id bypass the claim entirely (never dedup against
  the literal key 'undefined')

And records the per-bot update_id key scope in the model comment.
17/17 tests; mutation-checked both new tests kill their mutants.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X8MxGhdgPini3Q14ay2vXS
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Superseded: the same cherry-pick of 5062907 landed on main via #1468 (gated PASS by sprint-review, merged 22:20Z). Credit where due: this PR spotted the orphaned fix four hours before I did; I should have checked for it before opening #1468. Closing as duplicate, no action needed.

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