From 373618ad64c92d9c7f62b5c432c688a905bd320e Mon Sep 17 00:00:00 2001 From: Ahmett101 Date: Wed, 8 Jul 2026 07:06:43 +0300 Subject: [PATCH 1/2] fix(desktop): scope messaging and cron session fetches to active profile refreshCronSessions, refreshMessagingSessions, and loadMoreMessagingForPlatform all hardcoded profile='all' in their listAllProfileSessions() calls, causing sessions from all profiles to leak into the sidebar regardless of which profile is active. Apply the same profileScope resolution pattern already used by refreshSessions to the other three functions, and add profileScope to their useCallback dependency arrays. Closes #60678 --- .../app/session/hooks/use-session-list-actions.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/app/session/hooks/use-session-list-actions.ts b/apps/desktop/src/app/session/hooks/use-session-list-actions.ts index 6f971c3165be..da74e6d2afb6 100644 --- a/apps/desktop/src/app/session/hooks/use-session-list-actions.ts +++ b/apps/desktop/src/app/session/hooks/use-session-list-actions.ts @@ -83,7 +83,8 @@ export function useSessionListActions({ profileScope }: UseSessionListActionsArg // still resolves into the Pinned section via sessionByAnyId. const refreshCronSessions = useCallback(async () => { try { - const { sessions } = await listAllProfileSessions(CRON_SECTION_LIMIT, 1, 'exclude', 'recent', 'all', { + const sessionProfile = profileScope === ALL_PROFILES ? 'all' : profileScope + const { sessions } = await listAllProfileSessions(CRON_SECTION_LIMIT, 1, 'exclude', 'recent', sessionProfile, { source: 'cron' }) @@ -91,7 +92,7 @@ export function useSessionListActions({ profileScope }: UseSessionListActionsArg } catch { // Non-fatal: the cron section just stays empty/stale. } - }, []) + }, [profileScope]) // Messaging-platform sessions as their own slice, fetched separately from // local recents so each platform renders a self-managed section and never @@ -99,7 +100,8 @@ export function useSessionListActions({ profileScope }: UseSessionListActionsArg // seeds every platform; the sidebar splits the rows per source. const refreshMessagingSessions = useCallback(async () => { try { - const result = await listAllProfileSessions(MESSAGING_SECTION_LIMIT, 1, 'exclude', 'recent', 'all', { + const sessionProfile = profileScope === ALL_PROFILES ? 'all' : profileScope + const result = await listAllProfileSessions(MESSAGING_SECTION_LIMIT, 1, 'exclude', 'recent', sessionProfile, { excludeSources: MESSAGING_EXCLUDED_SOURCES }) @@ -114,7 +116,7 @@ export function useSessionListActions({ profileScope }: UseSessionListActionsArg } catch { // Non-fatal: the messaging sections just stay empty/stale. } - }, []) + }, [profileScope]) // Page a single platform's section independently (mirrors the per-profile // pager): fetch that source's next window and merge it back in place, leaving @@ -123,7 +125,8 @@ export function useSessionListActions({ profileScope }: UseSessionListActionsArg const inPlatform = (s: SessionInfo) => normalizeSessionSource(s.source) === platform const loaded = $messagingSessions.get().filter(inPlatform).length - const result = await listAllProfileSessions(loaded + SIDEBAR_SESSIONS_PAGE_SIZE, 1, 'exclude', 'recent', 'all', { + const sessionProfile = profileScope === ALL_PROFILES ? 'all' : profileScope + const result = await listAllProfileSessions(loaded + SIDEBAR_SESSIONS_PAGE_SIZE, 1, 'exclude', 'recent', sessionProfile, { source: platform }) @@ -136,7 +139,7 @@ export function useSessionListActions({ profileScope }: UseSessionListActionsArg const total = result.total ?? incoming.length setMessagingPlatformTotals(prev => ({ ...prev, [platform]: Math.max(total, incoming.length) })) - }, []) + }, [profileScope]) // Cron *jobs* drive the sidebar "Cron jobs" section. Jobs are created // synchronously (agent tool call or the cron UI), so refreshing here right From fb3cbb55c012d0116ec1e10c252d8bae650c7ab3 Mon Sep 17 00:00:00 2001 From: Ahmett101 <[email protected]> Date: Sat, 18 Jul 2026 01:57:52 +0300 Subject: [PATCH 2/2] fix(desktop): discard stale responses in cron/messaging/pagination session fetches --- .../session/hooks/use-session-list-actions.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/desktop/src/app/session/hooks/use-session-list-actions.ts b/apps/desktop/src/app/session/hooks/use-session-list-actions.ts index da74e6d2afb6..4dfa3e0590a8 100644 --- a/apps/desktop/src/app/session/hooks/use-session-list-actions.ts +++ b/apps/desktop/src/app/session/hooks/use-session-list-actions.ts @@ -76,18 +76,28 @@ interface UseSessionListActionsArgs { * wires into the sidebar and refresh effects. */ export function useSessionListActions({ profileScope }: UseSessionListActionsArgs) { const refreshSessionsRequestRef = useRef(0) + // Per-callback request refs guard against late responses overriding + // the active profile/closure pair when a profile switch happens + // mid-flight. Mirrors the same race in ``refreshSessions`` (#60678 + // review: stale-response guard). + const cronSessionsRequestRef = useRef(0) + const messagingSessionsRequestRef = useRef(0) + const messagingPageRequestRef = useRef(0) // Cron-job sessions as their own list (latest N). Independent of the recents // page so the two never compete for slots. Cheap + bounded. Kept (even though // the sidebar now lists cron *jobs*, not run sessions) so a pinned cron run // still resolves into the Pinned section via sessionByAnyId. const refreshCronSessions = useCallback(async () => { + const requestId = cronSessionsRequestRef.current + 1 + cronSessionsRequestRef.current = requestId try { const sessionProfile = profileScope === ALL_PROFILES ? 'all' : profileScope const { sessions } = await listAllProfileSessions(CRON_SECTION_LIMIT, 1, 'exclude', 'recent', sessionProfile, { source: 'cron' }) + if (cronSessionsRequestRef.current !== requestId) return setCronSessions(prev => (sameCronSignature(prev, sessions) ? prev : sessions)) } catch { // Non-fatal: the cron section just stays empty/stale. @@ -99,6 +109,8 @@ export function useSessionListActions({ profileScope }: UseSessionListActionsArg // competes with local chats for the recents page budget. One combined fetch // seeds every platform; the sidebar splits the rows per source. const refreshMessagingSessions = useCallback(async () => { + const requestId = messagingSessionsRequestRef.current + 1 + messagingSessionsRequestRef.current = requestId try { const sessionProfile = profileScope === ALL_PROFILES ? 'all' : profileScope const result = await listAllProfileSessions(MESSAGING_SECTION_LIMIT, 1, 'exclude', 'recent', sessionProfile, { @@ -109,6 +121,7 @@ export function useSessionListActions({ profileScope }: UseSessionListActionsArg // sources) — those stay in local recents, not a platform section. const rows = result.sessions.filter(s => isMessagingSource(s.source)) + if (messagingSessionsRequestRef.current !== requestId) return setMessagingSessions(prev => (sameCronSignature(prev, rows) ? prev : rows)) // Hit the cap → at least one platform may have more on disk than loaded, // so platform sections offer their own per-platform "load more". @@ -122,6 +135,8 @@ export function useSessionListActions({ profileScope }: UseSessionListActionsArg // pager): fetch that source's next window and merge it back in place, leaving // every other platform's rows untouched. Resolves the platform's exact total. const loadMoreMessagingForPlatform = useCallback(async (platform: string) => { + const requestId = messagingPageRequestRef.current + 1 + messagingPageRequestRef.current = requestId const inPlatform = (s: SessionInfo) => normalizeSessionSource(s.source) === platform const loaded = $messagingSessions.get().filter(inPlatform).length @@ -129,6 +144,7 @@ export function useSessionListActions({ profileScope }: UseSessionListActionsArg const result = await listAllProfileSessions(loaded + SIDEBAR_SESSIONS_PAGE_SIZE, 1, 'exclude', 'recent', sessionProfile, { source: platform }) + if (messagingPageRequestRef.current !== requestId) return const incoming = result.sessions.filter(s => normalizeSessionSource(s.source) === platform)