Skip to content

fix: group invites time mismatch#192

Merged
highesttt merged 2 commits into
mainfrom
highest/bios-35020
Jun 24, 2026
Merged

fix: group invites time mismatch#192
highesttt merged 2 commits into
mainfrom
highest/bios-35020

Conversation

@highesttt

Copy link
Copy Markdown
Collaborator

No description provided.

@linear-code

linear-code Bot commented Jun 24, 2026

Copy link
Copy Markdown

BIOS-35020

@indent

indent Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor
PR Summary

Pre-creates Matrix portals for existing LINE groups during startup so that the first prefetched/SSE message no longer triggers portal creation — which previously caused the message sender's pre-existing group membership to surface as a "joined the chat" timeline event on Beeper. Also adds a per-message safety net (a messageWithChatInfo wrapper that satisfies bridgev2.RemoteChatResyncWithInfo and supplies stripped chat info on demand) and a PreHandleFunc that joins the sender ghost with com.beeper.exclude_from_timeline so any join still happens silently.

  • client.go: syncChats now runs synchronously inside Connect (before prefetchMessages/pollLoop goroutines start) so group portals exist before messages can arrive.
  • sync.go syncChats: forces CreatePortal=true for every existing member group, calls stripRemoteMembersFromInitialChatInfo so brand-new portals are created with only the bridge user, then blocks on waitForGroupPortalCreates (shared 30 s budget on per-portal RoomCreated.Wait).
  • handle_message.go queueIncomingMessage: calls ensureGroupMessageSenderKnown before queueing, attaches a PreHandleFunc that hidden-joins the sender via portal.ProcessChatInfoChange, and for group portals wraps the event in messageWithChatInfo whose GetChatInfo fetches+strips the LINE chat info during on-demand portal creation.
  • sync.go group-member cache refactor: new addGroupMembersToCache/removeGroupMemberFromCache helpers; cacheGroupMembersFromSystemMessage now also handles C_MJ/A_MJ, C_ML/A_ML/C_MR/A_MR, C_IC, and A_MC; new cacheGroupMembersFromMessage dispatcher used by FetchMessages, backfillRecentMessages, and cacheGroupMembersFromRecentMessages; handleSelfLeave/handleMemberLeave/handleMemberJoin/handleSystemMessage updated to mutate the cache via the helpers.
  • chatToChatInfo and emitMemberChange now thread MemberEventExtra: {"com.beeper.exclude_from_timeline": true} (via new hiddenMemberEventExtra helper) and a variadic excludeFromTimeline flag so silent member updates carry the per-member extra in addition to the list-wide flag.

Issues

5 potential issues found:

  • emitMemberChange(..., excludeFromTimeline ...bool) uses a variadic bool for a single optional flag — only ever called with 0 or 1 argument; prefer a regular bool parameter (and update existing callers to pass false) or split into a separate helper. → Autofix
  • hiddenJoinGroupMessageSender runs portal.ProcessChatInfoChange on every inbound group message (ignoring addGroupMembersToCache's return value), which always performs GetPowerLevels + GetMembers lookups before deciding "already joined, no-op" — wasted work on every steady-state message. → Autofix
  • Connect() now runs syncChats synchronously (plus up to 30 s of waitForGroupPortalCreates), and bridge.StartLogins calls each login's Connect serially, so per-login startup blocks the next login — worth confirming this is acceptable for multi-login deployments or moving the wait into a goroutine that pollLoop/prefetchMessages rendezvous with. → Autofix
  • waitForGroupPortalCreates shares a single 30 s context across all pending portals and returns on the first Wait error, so a single slow/timed-out portal both consumes the whole budget and skips every remaining portal in the list. → Autofix
  • First inbound group message from a new sender now triggers two parallel "join" paths — ensureGroupMessageSenderKnown queues a RemoteEventChatInfoChange, and the PreHandleFunc synchronously calls portal.ProcessChatInfoChange — so the same hidden join is processed twice; pick one mechanism or have the PreHandle path early-return when the cache add was a no-op. → Autofix

CI Checks

Waiting for CI checks...


⚡ Autofix All Issues

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d21ef670-d464-4137-8ee4-4a4ffa13ce7a

📥 Commits

Reviewing files that changed from the base of the PR and between f5d0326 and 5e12208.

📒 Files selected for processing (1)
  • pkg/connector/sync.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/connector/sync.go
📜 Recent review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: build-docker
  • GitHub Check: Lint with 1.25
  • GitHub Check: Lint with 1.25
  • GitHub Check: build-docker

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Enhanced connection startup to ensure group chat portals are created and resynced earlier, improving portal availability.
    • Incoming messages now resolve and attach more precise chat info, improving where conversations open.
  • Bug Fixes

    • Improved group member caching during backfills and syncs for more accurate member state.
    • Reduced timeline noise by hiding internal membership join/leave updates from user-facing history.

Walkthrough

syncChats now runs synchronously during startup before other background goroutines. Group member caching is updated from all backfill messages and membership events. Incoming group messages can resolve chat info per message, and membership metadata now includes timeline-exclusion markers.

Changes

Group sync and member cache overhaul

Layer / File(s) Summary
New constants and group-membership utility helpers
pkg/connector/sync.go
Adds groupPortalCreateWait and beeperExcludeFromTimelineKey, then introduces group-membership helpers for own-MID detection, hidden member event metadata, self-member construction, remote-member filtering, cache mutation, and hidden sender handling.
Group member cache updates in backfill and handlers
pkg/connector/sync.go
Changes backfill loops to cache group members from every message, refactors system-message cache updates to direct add/remove mutations, updates recent-message caching to delegate per message, and mutates groupMemberCache before emitting self-leave, member-leave, member-join, join, leave, invite-cancel, and auto-join events.
syncChats portal creation and member metadata
pkg/connector/sync.go
Precomputes member-chat sets and pending portal creations, probes for existing portals, strips remote members from initial ChatInfo when needed, waits for startup portal creation, and attaches hidden member event metadata to chat member entries.
Startup ordering in Connect
pkg/connector/client.go
Runs lc.syncChats(ctx) synchronously after wg.Add(1), then starts syncDMChats, prefetchMessages, and pollLoop after wg.Add(3).
Per-message chat-info resolution for incoming group messages
pkg/connector/handle_message.go
Adds messageWithChatInfo and getChatInfoForIncomingMessage, then refactors queueIncomingMessage to set portal data earlier, add a PreHandleFunc for hidden sender joins, and wrap chat-MID destinations with per-message chat-info lookup.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • beeper/line#182: Modifies backfillRecentMessages in pkg/connector/sync.go in the same message-iteration path where this PR changes group-member cache updates.
🚥 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 pull request description was provided, so there is no meaningful description to assess. Add a brief description of the user-facing problem and the main sync/invite changes.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and aligns with the group invite/sync timing changes in the patch.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/bios-35020

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

Comment thread pkg/connector/sync.go
if !isChatMID(chatMid) || !isUserMID(senderMid) || lc.isOwnMID(senderMid) {
return
}
lc.addGroupMembersToCache(chatMid, senderMid)

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.

Hidden join fires on every group message. addGroupMembersToCache returns whether the sender was actually newly added, but we discard it and unconditionally call portal.ProcessChatInfoChange(...). Internally syncParticipants (mautrix portal.go:~4601) always runs Bridge.Matrix.GetPowerLevels + GetMembers before noticing the membership is unchanged — so every steady-state group message pays for two Matrix state lookups. ensureGroupMessageSenderKnown already gates on the same boolean; mirroring it here (if !lc.addGroupMembersToCache(chatMid, senderMid) { return }) drops the work to once per (chat, sender) pair, matching the only state change worth signalling.

Comment thread pkg/connector/sync.go
Err(err).
Object("portal_key", portal.PortalKey).
Msg("Timed out waiting for startup group portal creation")
return

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.

Single timeout aborts the whole wait loop. waitCtx is built once outside the loop with context.WithTimeout(ctx, groupPortalCreateWait) (30s), so all portals share the same deadline; if the first portal takes the full 30s (or hits ctx cancellation), Wait returns an error and this return skips every remaining portal in portals[]. The messageWithChatInfo wrapper does cover the race, but the bare return reads like a bug — either continue to keep checking the rest, or (better) move the context.WithTimeout(ctx, groupPortalCreateWait) inside the loop so each portal gets its own 30s budget.

Timestamp: ts,
PreHandleFunc: func(ctx context.Context, portal *bridgev2.Portal) {
lc.hiddenJoinGroupMessageSender(ctx, portal, portalIDStr, msg.From, ts)
},

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.

Duplicate first-message join paths. On line 119 we call ensureGroupMessageSenderKnown(...) (queues a RemoteEventChatInfoChange if addGroupMembersToCache returned true), and here we also wire PreHandleFunc → hiddenJoinGroupMessageSender(...), which synchronously calls portal.ProcessChatInfoChange. For the first message from a new sender both fire and both go through syncParticipants; for every subsequent message the PreHandle one still does the full GetPowerLevels+GetMembers work (see other comment). Consider keeping only one of the two, or at minimum have hiddenJoinGroupMessageSender honour addGroupMembersToCache's return value so it short-circuits after the first message.

Comment thread pkg/connector/client.go
// doesn't exist yet may be created by the first message, which makes the
// sender's existing membership look like a fresh join.
lc.wg.Add(1)
lc.syncChats(ctx)

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.

Synchronous syncChats blocks startup of every other login. mautrix's bridge.StartLogins (bridge.go:373) calls login.Client.Connect(ctx) for each login in sequence; previously all four sync goroutines were launched non-blocking and Connect returned almost immediately. Now Connect waits for syncChats to finish queueing every group plus up to 30s for portal creation (waitForGroupPortalCreates). For a single-login bridge that's fine; on a multi-login install all subsequent logins are delayed by that per-login window. If you want the ordering guarantee without blocking startup, you can keep syncChats in a goroutine and have prefetchMessages + pollLoop wait on a shared exsync.Event set when waitForGroupPortalCreates returns.

Comment thread pkg/connector/sync.go
}

func (lc *LineClient) emitMemberChange(chatMid, userMid string, membership event.Membership, ts time.Time) {
func (lc *LineClient) emitMemberChange(chatMid, userMid string, membership event.Membership, ts time.Time, excludeFromTimeline ...bool) {

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.

Nit — variadic bool is a code smell. This is only ever called with zero or one argument; the variadic is doing nothing except hiding an optional bool parameter. Either make it a regular bool (and update existing callers to pass false explicitly) or add a separate emitHiddenMemberChange helper. Easier to grep, easier to read at the call site.

@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: 3

🧹 Nitpick comments (1)
pkg/connector/sync.go (1)

999-1014: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Avoid replaying hidden joins for already-known senders.

hiddenJoinGroupMessageSender ignores addGroupMembersToCache’s result, so every message from a cached sender can re-run ProcessChatInfoChange. Gate this on a newly discovered sender or an explicit portal-membership miss, especially because ensureGroupMessageSenderKnown may seed the cache before this pre-handle path.

🤖 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 999 - 1014, The hidden join handling in
hiddenJoinGroupMessageSender is re-triggering ProcessChatInfoChange for senders
that are already cached because it ignores the result of addGroupMembersToCache.
Update this flow to only emit the hidden join when the sender is newly
discovered or when the portal membership lookup is missing, and use the existing
hiddenJoinGroupMessageSender, addGroupMembersToCache, and
ensureGroupMessageSenderKnown paths to gate the call accordingly.
🤖 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/client.go`:
- Around line 301-304: The startup WaitGroup accounting in connector client
setup is split around syncChats, which can let Disconnect() observe zero before
the remaining goroutines are registered. Update the initialization flow in
Client startup so all four slots are reserved before calling syncChats, using
the existing lc.wg and syncChats logic, then remove the later partial Add call
to keep shutdown and startup ordering safe.

In `@pkg/connector/handle_message.go`:
- Around line 132-134: The group-message pre-handler is emitting hidden joins on
every queued message because PreHandleFunc always calls
hiddenJoinGroupMessageSender, which then unconditionally triggers
ProcessChatInfoChange for non-own senders. Update the flow in handle_message.go
so hiddenJoinGroupMessageSender only runs when addGroupMembersToCache actually
adds a new member, or add a cache membership check before calling it, using the
existing hiddenJoinGroupMessageSender and addGroupMembersToCache paths to
prevent duplicate hidden join events.

In `@pkg/connector/sync.go`:
- Around line 785-788: The `cacheGroupMembersFromMessage` path is still skipping
non-bridgeable group messages because of the `isBridgeableContentType` guard,
which prevents sender caching for fallback/E2EE cases. Remove that early return
so `lc.addGroupMembersToCache(chatMid, msg.From)` runs for every non-system
group message, relying on `addGroupMembersToCache` to ignore non-user MIDs.

---

Nitpick comments:
In `@pkg/connector/sync.go`:
- Around line 999-1014: The hidden join handling in hiddenJoinGroupMessageSender
is re-triggering ProcessChatInfoChange for senders that are already cached
because it ignores the result of addGroupMembersToCache. Update this flow to
only emit the hidden join when the sender is newly discovered or when the portal
membership lookup is missing, and use the existing hiddenJoinGroupMessageSender,
addGroupMembersToCache, and ensureGroupMessageSenderKnown paths to gate the call
accordingly.
🪄 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: 5c7753f7-8fd4-49c8-98dd-1bed8f0c524f

📥 Commits

Reviewing files that changed from the base of the PR and between ab4ca90 and f5d0326.

📒 Files selected for processing (3)
  • pkg/connector/client.go
  • pkg/connector/handle_message.go
  • pkg/connector/sync.go
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: build-docker
  • GitHub Check: Lint with 1.25
  • GitHub Check: Lint with 1.25
  • GitHub Check: build-docker
🧰 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/connector/handle_message.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/connector/handle_message.go
  • pkg/connector/sync.go
🔇 Additional comments (4)
pkg/connector/sync.go (2)

31-32: LGTM!

Also applies to: 286-286, 424-424, 468-514, 535-553, 601-634, 761-784, 808-808, 837-983, 1730-1750, 1759-1784, 1897-1943


524-530: 🩺 Stability & Availability

Make the portal create path observable before this lookup. pendingPortalCreates only gets populated if GetExistingPortalByKey can see the new portal immediately after QueueRemoteEvent; if portal creation is deferred, first-time group portals will skip RoomCreated.Wait() and startup ordering won’t be enforced here. pkg/connector/sync.go:524-530

pkg/connector/handle_message.go (2)

77-100: LGTM!


115-131: LGTM!

Also applies to: 136-153

Comment thread pkg/connector/client.go
Comment on lines +301 to +304
lc.wg.Add(1)
lc.syncChats(ctx)

lc.wg.Add(3)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C3 'wg\.(Add|Done|Wait)\(|func \(lc \*LineClient\) (syncChats|syncDMChats|prefetchMessages|pollLoop|Disconnect)\b' pkg/connector

Repository: beeper/line

Length of output: 6067


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the startup / shutdown flow around Connect and Disconnect.
sed -n '250,360p' pkg/connector/client.go
printf '\n---\n'
sed -n '420,450p' pkg/connector/client.go
printf '\n---\n'
sed -n '1080,1145p' pkg/connector/sync.go
printf '\n---\n'
rg -n -C2 'Connect\(context\.Background\(\)\)|Disconnect\(\)|wg\.Wait\(|wg\.Add\(' pkg/connector

Repository: beeper/line

Length of output: 9939


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the startup / shutdown flow around Connect and Disconnect.
sed -n '250,360p' pkg/connector/client.go
printf '\n---\n'
sed -n '420,450p' pkg/connector/client.go
printf '\n---\n'
sed -n '1080,1145p' pkg/connector/sync.go
printf '\n---\n'
rg -n -C2 'Connect\(context\.Background\(\)\)|Disconnect\(\)|wg\.Wait\(|wg\.Add\(' pkg/connector

Repository: beeper/line

Length of output: 9939


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the startup / shutdown flow around Connect and Disconnect.
sed -n '250,360p' pkg/connector/client.go
printf '\n---\n'
sed -n '420,450p' pkg/connector/client.go
printf '\n---\n'
sed -n '1080,1145p' pkg/connector/sync.go
printf '\n---\n'
rg -n -C2 'Connect\(context\.Background\(\)\)|Disconnect\(\)|wg\.Wait\(|wg\.Add\(' pkg/connector

Repository: beeper/line

Length of output: 9939


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the startup / shutdown flow around Connect and Disconnect.
sed -n '250,360p' pkg/connector/client.go
printf '\n---\n'
sed -n '420,450p' pkg/connector/client.go
printf '\n---\n'
sed -n '1080,1145p' pkg/connector/sync.go
printf '\n---\n'
rg -n -C2 'Connect\(context\.Background\(\)\)|Disconnect\(\)|wg\.Wait\(|wg\.Add\(' pkg/connector

Repository: beeper/line

Length of output: 9939


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the startup / shutdown flow around Connect and Disconnect.
sed -n '250,360p' pkg/connector/client.go
printf '\n---\n'
sed -n '420,450p' pkg/connector/client.go
printf '\n---\n'
sed -n '1080,1145p' pkg/connector/sync.go
printf '\n---\n'
rg -n -C2 'Connect\(context\.Background\(\)\)|Disconnect\(\)|wg\.Wait\(|wg\.Add\(' pkg/connector

Repository: beeper/line

Length of output: 9939


Reserve all four WaitGroup slots before syncChats in pkg/connector/client.go:301-307. Disconnect() can return after syncChats drops the counter to zero, before the later Add(3), so the remaining goroutines may start after shutdown. Use Add(4) up front to cover all startup work.

🤖 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/client.go` around lines 301 - 304, The startup WaitGroup
accounting in connector client setup is split around syncChats, which can let
Disconnect() observe zero before the remaining goroutines are registered. Update
the initialization flow in Client startup so all four slots are reserved before
calling syncChats, using the existing lc.wg and syncChats logic, then remove the
later partial Add call to keep shutdown and startup ordering safe.

Comment on lines +132 to +134
PreHandleFunc: func(ctx context.Context, portal *bridgev2.Portal) {
lc.hiddenJoinGroupMessageSender(ctx, portal, portalIDStr, msg.From, ts)
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Avoid emitting a hidden join for every group message.

PreHandleFunc runs per queued message, while hiddenJoinGroupMessageSender unconditionally calls ProcessChatInfoChange for any non-own group sender. This can repeatedly send identical hidden membership joins for active group members. Please gate this on whether addGroupMembersToCache actually inserted a new member, or check the cache before calling the helper.

🤖 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/handle_message.go` around lines 132 - 134, The group-message
pre-handler is emitting hidden joins on every queued message because
PreHandleFunc always calls hiddenJoinGroupMessageSender, which then
unconditionally triggers ProcessChatInfoChange for non-own senders. Update the
flow in handle_message.go so hiddenJoinGroupMessageSender only runs when
addGroupMembersToCache actually adds a new member, or add a cache membership
check before calling it, using the existing hiddenJoinGroupMessageSender and
addGroupMembersToCache paths to prevent duplicate hidden join events.

Comment thread pkg/connector/sync.go
Comment on lines +785 to +788
if !isBridgeableContentType(msg) {
return
}
lc.groupMemberCache[chatMid] = members
lc.cacheMu.Unlock()
lc.addGroupMembersToCache(chatMid, msg.From)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Cache senders from every non-system group message.

cacheGroupMembersFromMessage is now called for every backfill message, but this guard still drops unsupported/non-bridgeable user messages. Since addGroupMembersToCache already filters non-user MIDs, removing it keeps the member cache complete for fallback/E2EE use.

Proposed fix
 	if msg.ContentType == 18 {
 		lc.cacheGroupMembersFromSystemMessage(msg)
 		return
 	}
-	if !isBridgeableContentType(msg) {
-		return
-	}
 	lc.addGroupMembersToCache(chatMid, msg.From)
📝 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
if !isBridgeableContentType(msg) {
return
}
lc.groupMemberCache[chatMid] = members
lc.cacheMu.Unlock()
lc.addGroupMembersToCache(chatMid, msg.From)
lc.addGroupMembersToCache(chatMid, msg.From)
🤖 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 785 - 788, The
`cacheGroupMembersFromMessage` path is still skipping non-bridgeable group
messages because of the `isBridgeableContentType` guard, which prevents sender
caching for fallback/E2EE cases. Remove that early return so
`lc.addGroupMembersToCache(chatMid, msg.From)` runs for every non-system group
message, relying on `addGroupMembersToCache` to ignore non-user MIDs.

@highesttt highesttt merged commit 399145f into main Jun 24, 2026
10 checks passed
@highesttt highesttt deleted the highest/bios-35020 branch June 24, 2026 13:53
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