Skip to content

feat(updates): apply affectedMessages/affectedHistory pts#1782

Merged
ernado merged 3 commits into
mainfrom
feat/updates-affected-pts
Jun 15, 2026
Merged

feat(updates): apply affectedMessages/affectedHistory pts#1782
ernado merged 3 commits into
mainfrom
feat/updates-affected-pts

Conversation

@ernado

@ernado ernado commented Jun 15, 2026

Copy link
Copy Markdown
Member

Fixes #1382.

Problem

A user account stops receiving an update (e.g. an edit) until the next pts-changing update arrives. Reproduction (from #1382): receive a message, read it, then have the other side edit it — the UpdateEditMessage is postponed.

Root cause: methods such as messages.readHistory, messages.deleteMessages, channels.deleteMessages, messages.readMentions, messages.deleteHistory return messages.affectedMessages / messages.affectedHistory, which carry a pts increment the client must apply. These results are not tg.UpdatesClass, so they never reach the updates manager — updhook.UpdateHook only forwards *tg.UpdatesBox results. The server pts advances while the local pts stays behind, so the next genuine pts update looks like a gap and is buffered:

pts  Gap detected            gap=[{from:4416,to:4417}]
pts  Out of gap range, postponed   upd_from:4418 upd_to:4418

The reporter's own messages.readHistory call (inside OnNewMessage) is what creates the missing pts. This matches TDLib, which applies the affected pts in each query's result handler.

Fix

  • Manager.HandleAffected(ctx, channelID, pts, ptsCount) — applies the increment to the common sequence, or to an already-tracked channel's sequence. It feeds a non-dispatchable affectedPts marker through the normal sequenceBox, so the pts advances (and fills any open gap, releasing postponed updates) without fabricating a user-visible update. applyPts skips the marker; an empty batch dispatches nothing but still persists pts.
    • pts == 0 is ignored — feeding 0 to a sequenceBox would reset its state to 0 and desync everything.
    • A channel affected pts for an untracked channel is dropped (its own getChannelDifference reconciles later).
  • hook.AffectedHook(handler) middleware — wires it from RPC results automatically. Routing by request: an InputChannel (channels.*) or InputPeerChannel peer (messages.* targeting a channel) → that channel; everything else → the common sequence.

Opt-in alongside UpdateHook:

telegram.Options{Middlewares: []telegram.Middleware{
    updhook.UpdateHook(gaps.Handle),
    hook.AffectedHook(gaps), // gaps is *updates.Manager
}}

Tests

  • TestHandleAffectedFillsGap reproduces Bug: update postponed and handled only with next update #1382: an edit postponed behind a missing read-pts is delivered (in order, exactly once) after the affected pts is applied.
  • TestHandleAffectedAdvancesPts / ZeroIgnored / UntrackedChannelIgnored cover the marker, the pts==0 guard, and channel gating.
  • TestAffectedHook covers result detection and common-vs-channel routing (by peer, by InputChannel, no-peer), plus RPC-error short-circuit.

All ./telegram/... tests pass, including -race on telegram/updates.

Notes

🤖 Generated with Claude Code

Methods like messages.readHistory, messages.deleteMessages and
channels.deleteMessages return messages.affectedMessages /
messages.affectedHistory, which carry a pts increment the client must
apply. These results are not tg.UpdatesClass, so they bypassed the
update manager entirely: the server pts advanced while the local pts
stayed behind, making the next genuine update (e.g. an edit) look like a
gap that was postponed until the next pts-changing update. Fixes #1382.

- Add Manager.HandleAffected(ctx, channelID, pts, ptsCount): applies the
  increment to the common sequence, or to a tracked channel's sequence,
  via a non-dispatchable affectedPts marker that advances pts without
  fabricating a user-visible update. pts==0 is ignored (it would reset
  the sequence to 0).
- Add hook.AffectedHook middleware that wires it from RPC results
  automatically, routing by request (InputChannel / InputPeerChannel ->
  channel; otherwise common).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 46.72131% with 65 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.15%. Comparing base (4793972) to head (cf95cfd).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
telegram/updates/state_channel.go 3.70% 24 Missing and 2 partials ⚠️
telegram/updates/state.go 37.50% 24 Missing and 1 partial ⚠️
telegram/updates/manager.go 0.00% 10 Missing ⚠️
telegram/updates/hook/affected.go 94.44% 1 Missing and 1 partial ⚠️
telegram/updates/state_apply.go 77.77% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1782      +/-   ##
==========================================
- Coverage   71.32%   71.15%   -0.18%     
==========================================
  Files         504      505       +1     
  Lines       23625    23748     +123     
==========================================
+ Hits        16851    16897      +46     
- Misses       5542     5612      +70     
- Partials     1232     1239       +7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Add hook.AffectedHook alongside the existing update middleware so the
updates/userbot examples keep local pts in sync after self-initiated
reads and deletes. See #1382.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a pts desynchronization bug where self-initiated RPCs returning messages.affectedMessages / messages.affectedHistory could advance server pts without advancing the local updates manager pts, causing subsequent pts-based updates (e.g., edits) to be buffered until another pts update arrives (issue #1382).

Changes:

  • Add an internal “non-dispatchable” pts marker (affectedPts) and plumbing (Manager.HandleAffected + state queues) to apply affected pts increments through the normal sequence handling without fabricating user-visible updates.
  • Add hook.AffectedHook middleware to automatically detect messages.affected* RPC results and route them to the correct pts sequence (common vs channel).
  • Add focused tests for gap-filling behavior and middleware detection/routing, plus update examples to show middleware usage.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
telegram/updates/update.go Introduces affectedPts marker type used to advance pts without dispatch.
telegram/updates/state.go Adds affected queue + handling in internal state loop; applies affected pts to common/channel sequences.
telegram/updates/state_channel.go Adds channel-side affected handling and ensures markers don’t dispatch.
telegram/updates/state_apply.go Skips dispatch on marker-only batches while still persisting pts.
telegram/updates/state_affected_test.go Adds tests reproducing #1382 and validating affected-pts semantics.
telegram/updates/manager.go Exposes Manager.HandleAffected API (no-op before Run).
telegram/updates/hook/affected.go Adds middleware to extract affected pts from RPC results and route by request.
telegram/updates/hook/affected_test.go Tests middleware detection and routing behavior.
examples/userbot/main.go Wires AffectedHook into userbot example middleware chain.
examples/updates/main.go Wires AffectedHook into updates example middleware chain.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread telegram/updates/hook/affected.go Outdated
Comment on lines +74 to +91
func channelIDFromRequest(input bin.Encoder) int64 {
if r, ok := input.(interface {
GetChannel() tg.InputChannelClass
}); ok {
if ch, ok := r.GetChannel().(*tg.InputChannel); ok {
return ch.ChannelID
}
return 0
}
if r, ok := input.(interface {
GetPeer() tg.InputPeerClass
}); ok {
if p, ok := r.GetPeer().(*tg.InputPeerChannel); ok {
return p.ChannelID
}
}
return 0
}
channelIDFromRequest only recognized *tg.InputChannel and
*tg.InputPeerChannel, so *tg.InputChannelFromMessage and
*tg.InputPeerChannelFromMessage (both carry a ChannelID) fell through to
the common sequence. That misapplied a channel pts to the common
sequence and could desync update processing.

Route via the shared GetChannelID() accessor (implemented by all four
channel inputs), and skip instead of defaulting to common when a
channels.* request carries no resolvable channel (e.g. InputChannelEmpty).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

@ernado ernado merged commit 8ffeaea into main Jun 15, 2026
15 checks passed
@ernado ernado deleted the feat/updates-affected-pts branch June 15, 2026 06:35
ernado added a commit to gotd/docs that referenced this pull request Jun 15, 2026
Cover updhook.AffectedHook from gotd/td#1782: add it to the wiring snippet and
explain why self-initiated reads/deletes (messages.affectedMessages /
affectedHistory) need it to keep local pts in sync and avoid postponed updates
(issue #1382).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

Bug: update postponed and handled only with next update

2 participants