eleven / messenger Public

web: skip keyless members when granting the instance key (#221)

A pending invitee who hasn't joined yet has no public key. grantInstanceKeyTo
looped the whole roster (connections() returns keyless entries for pending-DM
names) and called wrapThreadKey with an empty key, whose importKey throws
DataError: 'Data provided to an operation does not meet requirements'. That threw
inside boot's setupInstanceKey(), aborting chat load for everyone with the
'Something went wrong' card. Skip members without a public key; they get the
instance key from a holder's backfill once they join.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Paul Campbell d5424798fa26d48ff0151e2309c1cd023b6115e2 parent ce9563d
2 files changed, +10 −1
  • changelog/2026-07-05-fix-pending-invite-load.md +4 −0
  • web/static/shell.js +6 −1
diff --git a/changelog/2026-07-05-fix-pending-invite-load.md b/changelog/2026-07-05-fix-pending-invite-load.md
new file mode 100644
index 0000000..a836df6
--- /dev/null
+++ b/changelog/2026-07-05-fix-pending-invite-load.md
@@ -0,0 +1,4 @@
+Fixed a bug that could stop your chats from loading. If you had a pending invite
+out to someone who hadn't joined yet, the app could get stuck on "Something went
+wrong — couldn't load your chats." That's resolved: a not-yet-joined invitee no
+longer blocks anything, and picks up everything they need the moment they join.
diff --git a/web/static/shell.js b/web/static/shell.js
index 0ce5c88..50b8d7f 100644
--- a/web/static/shell.js
+++ b/web/static/shell.js
@@ -841,7 +841,12 @@ async function grantInstanceKeyTo(have) {
const haveSet = new Set(have);
const grants = [];
for (const e of roster) {
- if (!haveSet.has(e.user_id)) {
+ // Skip keyless members — a pending invitee who hasn't joined yet has no
+ // public key to wrap to (connections() returns them for pending-DM names).
+ // Without this guard wrapThreadKey's importKey throws DataError and aborts
+ // boot for *everyone*; they pick up the key from a holder's backfill once
+ // they join and publish one.
+ if (e.public_key && !haveSet.has(e.user_id)) {
grants.push({ user_id: e.user_id, wrapped: await wrapThreadKey(instanceKeyB64, e.public_key) });
}
}