fix(boot): skip stale local active-user seed in cloud mode (#4545) (#4704)

This commit is contained in:
CodeGhost21
2026-07-08 16:55:55 -07:00
committed by GitHub
parent bcdad327da
commit b9c88abe90
3 changed files with 150 additions and 8 deletions
+12 -8
View File
@@ -16,7 +16,9 @@ import { setStoreForApiClient } from './services/apiClient';
import { primeActiveUserId } from './store/userScopedStorage';
import './styles/code-highlight.css';
import './styles/theme.css';
import { resolveActiveUserBootstrap } from './utils/bootstrapActiveUser';
import { APP_VERSION } from './utils/config';
import { getStoredCoreMode } from './utils/configPersistence';
import { setupDesktopDeepLinkListener } from './utils/desktopDeepLinkListener';
import { missingHashRedirectTarget } from './utils/hashRouterBootstrap';
import { getActiveUserIdFromCore } from './utils/tauriCommands';
@@ -103,14 +105,16 @@ function bootRender() {
root.render(<React.StrictMode>{tree}</React.StrictMode>);
}
// The mascot and notch windows live in native WKWebViews (no Tauri IPC), so
// `getActiveUserIdFromCore()` would just reject after a roundtrip and
// delay first paint for nothing. Skip the bootstrap entirely in those
// paths — neither UI reads user-scoped storage.
const activeUserBootstrap =
isMascotWindow || isNotchWindow
? Promise.resolve<string | null>(null)
: getActiveUserIdFromCore();
// Decide which source (Rust IPC vs preserved localStorage seed) primes
// `userScopedStorage` at boot. Cloud mode and standalone native windows
// both fall through to `primeActiveUserId(null)`, which preserves whatever
// `setActiveUserId(...)` wrote in a prior session — see
// `resolveActiveUserBootstrap` for the full rationale (#4545, #900).
const activeUserBootstrap = resolveActiveUserBootstrap({
isStandaloneNativeWindow: isMascotWindow || isNotchWindow,
coreMode: getStoredCoreMode(),
getActiveUserIdFromCore,
});
activeUserBootstrap
.then(id => primeActiveUserId(id))
@@ -0,0 +1,92 @@
/**
* Regression tests for the active-user boot decision that gates #4545.
*
* The end-to-end loop the fix prevents (see `resolveActiveUserBootstrap`
* docblock):
* 1. User picks Cloud/Remote core → boot succeeds
* 2. `CoreStateProvider` fetches the remote snapshot → nextIdentity = REMOTE user
* 3. Boot primes seed from local `active_user.toml` → seedUserId = OLD LOCAL user
* 4. `seedUserId !== nextIdentity` → `handleIdentityFlip` → `restartApp`
* 5. Repeat forever
*
* The fix short-circuits step 3 whenever `coreMode === 'cloud'` (or the
* window is a standalone native mascot/notch webview with no Tauri IPC).
*/
import { describe, expect, test, vi } from 'vitest';
import { resolveActiveUserBootstrap, shouldSkipLocalActiveUserRead } from '../bootstrapActiveUser';
describe('shouldSkipLocalActiveUserRead', () => {
test('cloud mode skips the local read', () => {
expect(
shouldSkipLocalActiveUserRead({ isStandaloneNativeWindow: false, coreMode: 'cloud' })
).toBe(true);
});
test('local mode reads through', () => {
expect(
shouldSkipLocalActiveUserRead({ isStandaloneNativeWindow: false, coreMode: 'local' })
).toBe(false);
});
test('unset mode reads through (fresh install falls back to local file → likely null → prime(null))', () => {
expect(shouldSkipLocalActiveUserRead({ isStandaloneNativeWindow: false, coreMode: null })).toBe(
false
);
});
test('standalone native window skips regardless of core mode', () => {
expect(
shouldSkipLocalActiveUserRead({ isStandaloneNativeWindow: true, coreMode: 'local' })
).toBe(true);
expect(
shouldSkipLocalActiveUserRead({ isStandaloneNativeWindow: true, coreMode: 'cloud' })
).toBe(true);
});
});
describe('resolveActiveUserBootstrap', () => {
test('cloud mode resolves null WITHOUT calling getActiveUserIdFromCore (#4545)', async () => {
const getActiveUserIdFromCore = vi.fn().mockResolvedValue('stale-local-user');
const result = await resolveActiveUserBootstrap({
isStandaloneNativeWindow: false,
coreMode: 'cloud',
getActiveUserIdFromCore,
});
expect(result).toBeNull();
expect(getActiveUserIdFromCore).not.toHaveBeenCalled();
});
test('local mode delegates to getActiveUserIdFromCore', async () => {
const getActiveUserIdFromCore = vi.fn().mockResolvedValue('user-A');
const result = await resolveActiveUserBootstrap({
isStandaloneNativeWindow: false,
coreMode: 'local',
getActiveUserIdFromCore,
});
expect(result).toBe('user-A');
expect(getActiveUserIdFromCore).toHaveBeenCalledTimes(1);
});
test('unset mode delegates to getActiveUserIdFromCore (pre-picker cold boot)', async () => {
const getActiveUserIdFromCore = vi.fn().mockResolvedValue(null);
const result = await resolveActiveUserBootstrap({
isStandaloneNativeWindow: false,
coreMode: null,
getActiveUserIdFromCore,
});
expect(result).toBeNull();
expect(getActiveUserIdFromCore).toHaveBeenCalledTimes(1);
});
test('standalone native window resolves null WITHOUT calling the IPC', async () => {
const getActiveUserIdFromCore = vi.fn().mockResolvedValue('should-not-be-read');
const result = await resolveActiveUserBootstrap({
isStandaloneNativeWindow: true,
coreMode: 'local',
getActiveUserIdFromCore,
});
expect(result).toBeNull();
expect(getActiveUserIdFromCore).not.toHaveBeenCalled();
});
});
+46
View File
@@ -0,0 +1,46 @@
/**
* Decide which async source seeds `userScopedStorage`'s active-user id at
* boot, before `primeActiveUserId(...)` runs.
*
* Three source shapes:
* 1. Mascot/notch native windows — no Tauri IPC, cannot invoke commands.
* 2. Cloud/remote core mode — the local `~/.openhuman/active_user.toml`
* is either empty (no prior local session) or bound to a prior LOCAL
* session's user id. In both cases it doesn't match the REMOTE core's
* authenticated user, and priming from it overwrites the correct
* `localStorage` seed that `handleIdentityFlip` writes just before
* `restartApp`. That mismatch drives the infinite
* `identityFlip → restartApp` restart loop reported in #4545.
* 3. Local core mode — read the Rust `active_user.toml` via IPC. This is
* the profile-independent source of truth the local sidecar writes
* atomically during `auth_store_session` (#900).
*
* Cases (1) and (2) resolve `null`; `primeActiveUserId(null)` then preserves
* the existing `localStorage` seed rather than wiping it. See
* `userScopedStorage.ts::primeActiveUserId` and the "cloud-mode reload
* survival" test.
*/
export interface BootstrapContext {
isStandaloneNativeWindow: boolean;
coreMode: 'local' | 'cloud' | null;
getActiveUserIdFromCore: () => Promise<string | null>;
}
export function shouldSkipLocalActiveUserRead(opts: {
isStandaloneNativeWindow: boolean;
coreMode: 'local' | 'cloud' | null;
}): boolean {
return opts.isStandaloneNativeWindow || opts.coreMode === 'cloud';
}
export function resolveActiveUserBootstrap(ctx: BootstrapContext): Promise<string | null> {
if (
shouldSkipLocalActiveUserRead({
isStandaloneNativeWindow: ctx.isStandaloneNativeWindow,
coreMode: ctx.coreMode,
})
) {
return Promise.resolve<string | null>(null);
}
return ctx.getActiveUserIdFromCore();
}