Guard Matrix client lifecycle starts#43
Conversation
|
Deploying with
|
| Status | Preview URL | Commit | Alias | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! | https://pr-43-sable.justin-tech.workers.dev | 7da21a2 | pr-43 |
Sat, 13 Jun 2026 15:22:29 GMT |
There was a problem hiding this comment.
Pull request overview
This PR adds lifecycle guards around Matrix client startup/shutdown so the “app” Matrix client behaves like a singleton (preventing duplicate/concurrent starts), while allowing background notification clients to start independently. It also updates call sites to await/chain client shutdown where needed and adds regression tests for the new gating behavior.
Changes:
- Introduces an app-scoped
startClientgate to reuse in-flight starts and stop/replace any previously-active app client. - Changes
stopClientto return aPromise<void>(with a short settlement delay) and updates callers toawait/voidit appropriately. - Adds
clientScope: 'background'for background notification clients and tests covering the new app start gating.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/client/initMatrix.ts | Adds app-scope start gating, background scope opt-out, and makes stopClient async-like for sequencing. |
| src/client/initMatrix.test.ts | Adds tests for app singleton start behavior; expands Sentry mocks. |
| src/app/pages/client/sidebar/AccountSwitcherTab.tsx | Ensures navigation to “add account” happens after client stop completes (via finally). |
| src/app/pages/client/ClientRoot.tsx | Awaits/voids stopClient in lifecycle paths; avoids triggering redundant start while already loading. |
| src/app/pages/client/BackgroundNotifications.tsx | Marks background clients with clientScope: 'background' and prevents duplicate background starts per user. |
| if (activeAppClient && activeAppClient !== mx) { | ||
| debugLog.warn('sync', 'Stopping previous app Matrix client before starting replacement', { | ||
| previousUserId: activeAppClient.getUserId(), | ||
| nextUserId: mx.getUserId(), | ||
| previousSyncState: activeAppClient.getSyncState(), | ||
| previousRunning: activeAppClient.clientRunning, | ||
| }); | ||
| Sentry.addBreadcrumb({ | ||
| category: 'sync.lifecycle', | ||
| message: 'Stopping previous app Matrix client before replacement start', | ||
| level: 'warning', | ||
| data: { | ||
| previousUserId: activeAppClient.getUserId(), | ||
| nextUserId: mx.getUserId(), | ||
| previousSyncState: activeAppClient.getSyncState(), | ||
| previousRunning: activeAppClient.clientRunning, | ||
| }, | ||
| }); | ||
| Sentry.metrics.count('sable.sync.previous_app_client_stopped', 1); | ||
| await stopClient(activeAppClient); | ||
| } |
| const startClassicClient = async (mx: MockMatrixClient): Promise<void> => { | ||
| startedClients.push(mx); | ||
| await startClient(mx, { |
Description
Fixes #
Type of change
Checklist:
AI disclosure: