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>
2 files changed,
+10
−1
changelog/2026-07-05-fix-pending-invite-load.md+4 −0web/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) }); |
| } |
| } |