From 91b4a4eb98c928ee4f027d212775694acf83a279 Mon Sep 17 00:00:00 2001 From: yewreeka Date: Fri, 12 Jun 2026 09:01:49 -0700 Subject: [PATCH] fix: process join requests from already-consented DMs Join requests arrive on a joiner->creator DM. Processing a request sets that DM's consent to Allowed, but the batch and catchup scans only listed DMs with Unknown consent - so any later join request from the same joiner (e.g. joining a second conversation from the same creator) was silently dropped. The joiner was never added and never received an error, leaving clients stuck on the "verifying" screen indefinitely. Include Allowed DMs in both scans, and skip requests whose sender is already a member so re-scanned requests stay idempotent (previously the consent flip itself acted as the implicit dedup). Verified against production: a device whose second join request had been sitting unprocessed on an Allowed DM was added immediately by the patched scan, while its already-processed first request was skipped. Co-Authored-By: Claude Fable 5 --- .../conversations/process-join-requests.ts | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/commands/conversations/process-join-requests.ts b/src/commands/conversations/process-join-requests.ts index 916056d..d7973b0 100644 --- a/src/commands/conversations/process-join-requests.ts +++ b/src/commands/conversations/process-join-requests.ts @@ -181,6 +181,17 @@ always-on usage).`; // If we can't read appData, skip tag check but continue } + // Scanning Allowed DMs means previously handled join requests come around + // again on every batch/catchup pass; an existing membership makes them a + // no-op instead of a re-add. + const existingMembers = await group.members(); + if (existingMembers.some((m) => m.inboxId === message.senderInboxId)) { + this.log( + `${message.senderInboxId} already a member of ${conversationId} — skipping`, + ); + return; + } + this.log(`Adding ${message.senderInboxId} to conversation ${conversationId}`); await group.addMembers([message.senderInboxId]); @@ -242,9 +253,12 @@ always-on usage).`; if (sinceNs === 0n) return; try { await client.conversations.sync(); + // Include Allowed DMs: a repeat joiner's request arrives on the DM that + // was consented during their first join. Scanning only Unknown DMs + // silently drops those requests, leaving the joiner stuck "verifying". const dms = await client.conversations.list({ conversationType: ConversationType.Dm, - consentStates: [ConsentState.Unknown], + consentStates: [ConsentState.Unknown, ConsentState.Allowed], }); for (const dm of dms) { try { @@ -313,9 +327,12 @@ always-on usage).`; const allResults = []; await client.conversations.sync(); + // Include Allowed DMs: a repeat joiner's request arrives on the DM that + // was consented during their first join. Scanning only Unknown DMs + // silently drops those requests, leaving the joiner stuck "verifying". const dms = await client.conversations.list({ conversationType: ConversationType.Dm, - consentStates: [ConsentState.Unknown], + consentStates: [ConsentState.Unknown, ConsentState.Allowed], }); for (const dm of dms) {