Skip to content

fix: backfill only worked for the 1st page of members#194

Merged
highesttt merged 1 commit into
mainfrom
highest/plat-37699
Jun 28, 2026
Merged

fix: backfill only worked for the 1st page of members#194
highesttt merged 1 commit into
mainfrom
highest/plat-37699

Conversation

@highesttt

Copy link
Copy Markdown
Collaborator

No description provided.

@linear-code

linear-code Bot commented Jun 28, 2026

Copy link
Copy Markdown

PLAT-37699

@indent-zero

indent-zero Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor
PR Summary

Fixes startup backfill being silently capped at the first 100 LINE message boxes — accounts with more chats had the rest skipped. The fix paginates getMessageBoxes via a new minChatId cursor and unions the resulting message-box MIDs with known group/room MIDs from getAllChatMids, so inactive groups still get a startup backfill pass.

  • Added MinChatID (minChatId, omitempty) to line.MessageBoxesOptions and a MessageBoxPageLimit / StartupBackfillMessageLimit constant pair.
  • New getMessageBoxesWithRecovery + fetchAllMessageBoxes helpers consolidate the token-recover/retry pattern and walk pages until HasNext is false (with a seenCursors guard against non-advancing pagination).
  • syncDMChats and prefetchMessages now use the paginated helper; prefetchMessages builds its work list via the new collectStartupBackfillChatMIDs which dedupes message-box MIDs with knownMemberChatMIDs, skipping blocked users.
  • syncChats records the latest MemberChatMids into a new LineClient.knownMemberChatMIDs map (cacheMu-guarded, filtered via isChatMID) so prefetch can see groups absent from the message-box window.

Issues

2 potential issues found:

  • Latent (triggers if minChatId is inclusive on LINE's side): the seenCursors guard in fetchAllMessageBoxes only catches a non-advancing cursor; if pages overlap by one box, syncDMChats will emit ChatResync twice for the boundary DM since it iterates messageBoxes without dedup. Consider deduping by box.ID in syncDMChats (or in fetchAllMessageBoxes) the same way collectStartupBackfillChatMIDs already does. → Autofix
  • Nit: syncChats does not reset knownMemberChatMIDs when GetAllChatMids errors after recovery — on a re-Connect that hits a transient failure, prefetchMessages would consume the previous cycle's stale list. Today it just means an extra (harmless) backfill attempt; calling setKnownMemberChatMIDs(nil) on the error path keeps the cache honest with its comment ("current member chats returned by getAllChatMids"). → Autofix

CI Checks

Waiting for CI checks...


⚡ Autofix All Issues

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Improved startup message loading to fetch chats more completely and backfill recent messages more reliably.
    • Added support for more precise message-box filtering when syncing chats.
  • Bug Fixes

    • Reduced missed or duplicated chat data during sync by reusing cached chat membership information.
    • Improved recovery when message fetching needs to refresh authentication, helping sync continue more smoothly.

Walkthrough

Adds MinChatID to MessageBoxesOptions for cursor-based pagination, introduces a knownMemberChatMIDs cache field on LineClient, and refactors startup DM sync and message prefetch to use a shared fetchAllMessageBoxes paginator and a new collectStartupBackfillChatMIDs helper that dedupes and merges chat MIDs from message boxes and member chat MIDs.

Paginated message-box sync and member chat MID cache

Layer / File(s) Summary
MessageBoxesOptions and LineClient cache field
pkg/line/structs.go, pkg/connector/client.go
MessageBoxesOptions gains MinChatID string for pagination cursors. LineClient adds knownMemberChatMIDs map[string]struct{} protected by cacheMu, initialized in Connect.
Paginated message-box fetch helpers
pkg/connector/sync.go
New constants messageBoxPageLimit and startupBackfillMessageLimit; getMessageBoxesWithRecovery wraps token-refresh recovery; fetchAllMessageBoxes iterates pages using MinChatID as cursor with dedup.
syncDMChats and syncChats integration
pkg/connector/sync.go
syncDMChats replaces its inline single-call fetch with fetchAllMessageBoxes. syncChats calls setKnownMemberChatMIDs after each sync; new setKnownMemberChatMIDs/getKnownMemberChatMIDs helpers manage the cache under cacheMu.
Reworked prefetchMessages and collectStartupBackfillChatMIDs
pkg/connector/sync.go
prefetchMessages fetches all message boxes via paginator, calls collectStartupBackfillChatMIDs to build a deduped list filtering blocked contacts and merging knownMemberChatMIDs, then runs a worker pool backfilling each chat MID with startupBackfillMessageLimit.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • beeper/line#181: Modifies the same DM backfill/prefetch worker-pool pipeline in pkg/connector/sync.go, including blocked-chat handling.
  • beeper/line#182: Overlaps in pkg/connector/sync.go startup backfill selection and backfillRecentMessages retry logic.
  • beeper/line#192: Directly related Connect sequencing and syncChats-driven membership caching in the same files.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive No description was provided, so there is no meaningful change summary to evaluate. Add a brief description of the backfill and pagination changes to help reviewers understand the PR.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main fix: startup backfill now handles all member pages instead of only the first.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch highest/plat-37699

Comment @coderabbitai help to get the list of available commands.

Comment thread pkg/connector/sync.go
}

for _, box := range res.MessageBoxes {
for _, box := range messageBoxes {

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.

Latent: duplicate ChatResync on overlapping pages. fetchAllMessageBoxes uses the last box's ID as the next MinChatID; if LINE treats minChatId as inclusive (a common cursor convention — the protocol isn't documented either way), each page will repeat the previous page's last box, and syncDMChats will emit ChatResync twice for that DM. seenCursors only stops the loop when the cursor doesn't advance at all, so overlap-by-one passes through. The framework should treat ChatResync as idempotent so this is benign today, but prefetchMessages already dedupes via collectStartupBackfillChatMIDs — consider doing the same here for symmetry, or dropping the last box from each non-final page in fetchAllMessageBoxes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/connector/sync.go`:
- Around line 65-70: The pagination logic in sync/backfill helpers is treating
an empty or repeated cursor as a successful end-of-data case, which can silently
truncate results. Update the cursor handling in the helper that reads
res.MessageBoxes and tracks seenCursors so that when HasNext is true but
nextCursor is empty or already seen, it returns an error instead of boxes, nil.
Make sure callers can distinguish a broken cursor from a normal completion by
surfacing a clear failure from this pagination path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cc0e8fb9-23b5-4bcf-b59d-f28aa4a5f077

📥 Commits

Reviewing files that changed from the base of the PR and between 461e334 and 4457c48.

📒 Files selected for processing (3)
  • pkg/connector/client.go
  • pkg/connector/sync.go
  • pkg/line/structs.go
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: Lint with 1.25
  • GitHub Check: build-docker
  • GitHub Check: build-docker
  • GitHub Check: Lint with 1.25
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Use go fmt for code formatting across all Go files
Use goimports with -local "github.com/highesttt/matrix-line-messenger" flag to group project-local imports correctly
Use zerolog for logging throughout the codebase
Do not use Msgf in logging; use Msg with structured fields instead
Use Stringer interface where applicable in Go code

Files:

  • pkg/connector/client.go
  • pkg/line/structs.go
  • pkg/connector/sync.go
**/!(ltsm)/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/!(ltsm)/**/*.go: Run staticcheck on all Go files excluding pkg/ltsm package (transpiled WASM code)
Run go vet on all Go files excluding pkg/ltsm package (transpiled WASM code)

Files:

  • pkg/connector/client.go
  • pkg/line/structs.go
  • pkg/connector/sync.go
🔇 Additional comments (2)
pkg/line/structs.go (1)

276-281: LGTM!

pkg/connector/client.go (1)

37-47: LGTM!

Also applies to: 261-263

Comment thread pkg/connector/sync.go
Comment on lines +65 to +70
nextCursor := res.MessageBoxes[len(res.MessageBoxes)-1].ID
if nextCursor == "" {
return boxes, nil
}
if _, ok := seenCursors[nextCursor]; ok {
return boxes, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Don't treat a broken pagination cursor as success.

If HasNext is still true but the API returns an empty or repeated ID, this helper returns boxes, nil and the callers proceed with a silently truncated sync/backfill result. That recreates the “first page only” failure mode without any signal.

Suggested fix
 		nextCursor := res.MessageBoxes[len(res.MessageBoxes)-1].ID
 		if nextCursor == "" {
-			return boxes, nil
+			return nil, fmt.Errorf("message-box pagination returned empty cursor with hasNext=true")
 		}
 		if _, ok := seenCursors[nextCursor]; ok {
-			return boxes, nil
+			return nil, fmt.Errorf("message-box pagination cursor %q repeated", nextCursor)
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
nextCursor := res.MessageBoxes[len(res.MessageBoxes)-1].ID
if nextCursor == "" {
return boxes, nil
}
if _, ok := seenCursors[nextCursor]; ok {
return boxes, nil
nextCursor := res.MessageBoxes[len(res.MessageBoxes)-1].ID
if nextCursor == "" {
return nil, fmt.Errorf("message-box pagination returned empty cursor with hasNext=true")
}
if _, ok := seenCursors[nextCursor]; ok {
return nil, fmt.Errorf("message-box pagination cursor %q repeated", nextCursor)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/connector/sync.go` around lines 65 - 70, The pagination logic in
sync/backfill helpers is treating an empty or repeated cursor as a successful
end-of-data case, which can silently truncate results. Update the cursor
handling in the helper that reads res.MessageBoxes and tracks seenCursors so
that when HasNext is true but nextCursor is empty or already seen, it returns an
error instead of boxes, nil. Make sure callers can distinguish a broken cursor
from a normal completion by surfacing a clear failure from this pagination path.

@highesttt highesttt merged commit be98960 into main Jun 28, 2026
9 checks passed
@highesttt highesttt deleted the highest/plat-37699 branch June 28, 2026 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant