From 0e33c474b40329aa83c43f452b5aa3405364d619 Mon Sep 17 00:00:00 2001 From: Erwan Leboucher Date: Mon, 31 Aug 2026 11:23:08 +0200 Subject: [PATCH 1/2] fix(notifications): de-duplicate Sentry logs and surface push decrypt failures --- src/app/crypto/pushDecrypt.ts | 5 ++--- src/app/pages/client/BackgroundNotifications.tsx | 8 +------- src/instrument.ts | 1 + 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/app/crypto/pushDecrypt.ts b/src/app/crypto/pushDecrypt.ts index 8597a12b7..84ef446ba 100644 --- a/src/app/crypto/pushDecrypt.ts +++ b/src/app/crypto/pushDecrypt.ts @@ -59,11 +59,10 @@ export const decryptPushEventNatively = async ( sender: decrypted.sender ?? event.sender, }; } catch (error) { - // Expected while the to-device key is still in flight, so not a warning. - pushDecryptLog.info( + pushDecryptLog.warn( 'notification', 'Native push decryption unavailable, falling back to the js-sdk path', - error + { reason: error instanceof Error ? error.message : String(error) } ); return null; } diff --git a/src/app/pages/client/BackgroundNotifications.tsx b/src/app/pages/client/BackgroundNotifications.tsx index 526ffc77c..57dc4c241 100644 --- a/src/app/pages/client/BackgroundNotifications.tsx +++ b/src/app/pages/client/BackgroundNotifications.tsx @@ -592,13 +592,7 @@ export function BackgroundNotifications() { .catch((err) => { if (disposed) return; log.error('failed to start background client for', session.userId, err); - debugLog.error('notification', 'Failed to start background client', { - userId: session.userId, - error: err, - }); - Sentry.captureException(err, { - tags: { component: 'BackgroundNotifications' }, - }); + debugLog.error('notification', 'Failed to start background client', err); // Remove the stuck/failed client from current so future runs (or the // retry below) can attempt a fresh start. diff --git a/src/instrument.ts b/src/instrument.ts index f8029284a..25cde598b 100644 --- a/src/instrument.ts +++ b/src/instrument.ts @@ -99,6 +99,7 @@ if (dsn && sentryEnabled) { beforeSendLog(log) { // Drop debug-level logs in production to reduce noise and quota usage if (log.level === 'debug' && environment === 'production') return null; + if (typeof log.message === 'string' && log.message.startsWith('[sable:')) return null; // Redact Matrix IDs and tokens from the log message string if (typeof log.message === 'string') { log.message = scrubMatrixIds(log.message); From 82f31ac92119ed2f167adcd7392f92a2993cdf12 Mon Sep 17 00:00:00 2001 From: Erwan Leboucher Date: Mon, 31 Aug 2026 11:43:56 +0200 Subject: [PATCH 2/2] fix(crypto): send the backup version when fetching a session key from backup --- src/app/crypto/engineCrypto/EngineCrypto.ts | 1 + .../perSessionBackupDownload.test.ts | 18 ++++++++++++++++++ .../engineCrypto/perSessionBackupDownload.ts | 9 ++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/app/crypto/engineCrypto/EngineCrypto.ts b/src/app/crypto/engineCrypto/EngineCrypto.ts index 05ff58a86..4f256d4e6 100644 --- a/src/app/crypto/engineCrypto/EngineCrypto.ts +++ b/src/app/crypto/engineCrypto/EngineCrypto.ts @@ -366,6 +366,7 @@ export class EngineCrypto this.#identity = identity; this.#backupDownloader = new PerSessionBackupDownloader({ mx, + getBackupVersion: () => this.getActiveSessionBackupVersion().catch(() => null), importSession: (roomId, session) => this.#importBackedUpSession(roomId, session), now: () => Date.now(), }); diff --git a/src/app/crypto/engineCrypto/perSessionBackupDownload.test.ts b/src/app/crypto/engineCrypto/perSessionBackupDownload.test.ts index d926905ea..3ab00c6ad 100644 --- a/src/app/crypto/engineCrypto/perSessionBackupDownload.test.ts +++ b/src/app/crypto/engineCrypto/perSessionBackupDownload.test.ts @@ -18,9 +18,11 @@ const rateLimited = (retryAfterMs: number) => describe('PerSessionBackupDownloader', () => { let clock = 0; + let backupVersion: string | null = '7'; beforeEach(() => { clock = 0; + backupVersion = '7'; }); const make = ( @@ -31,6 +33,7 @@ describe('PerSessionBackupDownloader', () => { ) => { const downloader = new PerSessionBackupDownloader({ mx: { http: { authedRequest } } as unknown as MatrixClient, + getBackupVersion: async () => backupVersion, importSession, now: () => clock, }); @@ -48,9 +51,24 @@ describe('PerSessionBackupDownloader', () => { expect(authedRequest).toHaveBeenCalledTimes(1); expect(authedRequest.mock.calls[0]?.[1]).toBe('/room_keys/keys/!r%3Ae.org/S1'); + expect(authedRequest.mock.calls[0]?.[2]).toEqual({ version: '7' }); expect(importSession).toHaveBeenCalledTimes(1); }); + it('does not query the backup when no active version is known', async () => { + backupVersion = null; + const authedRequest = vi.fn<(...args: never[]) => Promise>(async () => ({ + session_data: {}, + })); + const { downloader, importSession } = make(authedRequest); + + downloader.request({ roomId: '!r:e.org', sessionId: 'S1' }); + await settle(); + + expect(authedRequest).not.toHaveBeenCalled(); + expect(importSession).not.toHaveBeenCalled(); + }); + it('does not hammer the backup for a session it is already fetching', async () => { const authedRequest = vi.fn<(...args: never[]) => Promise>(async () => ({ session_data: {}, diff --git a/src/app/crypto/engineCrypto/perSessionBackupDownload.ts b/src/app/crypto/engineCrypto/perSessionBackupDownload.ts index 12d75eff9..da92c962b 100644 --- a/src/app/crypto/engineCrypto/perSessionBackupDownload.ts +++ b/src/app/crypto/engineCrypto/perSessionBackupDownload.ts @@ -9,6 +9,7 @@ export type SessionRef = { roomId: string; sessionId: string }; export type BackupDownloadHost = { mx: MatrixClient; + getBackupVersion: () => Promise; importSession: (roomId: string, session: KeyBackupSession) => Promise; now: () => number; }; @@ -91,11 +92,17 @@ export class PerSessionBackupDownloader { $sessionId: ref.sessionId, }); + const version = await this.#host.getBackupVersion(); + if (!version) { + this.#missingUntil.set(key, this.#host.now() + BACKOFF_TIME_MS); + return; + } + try { const session = await this.#host.mx.http.authedRequest( Method.Get, path, - {}, + { version }, undefined, { prefix: ClientPrefix.V3 } );