From 706ecd7434fc4fbd8da7e9ef8bc7a5392f5efb46 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 30 May 2026 01:05:56 -0700 Subject: [PATCH] fix: guard daemon session async state (#3543) --- .../components/shared/useDaemonActions.tsx | 40 ++++++++++++++----- .../status-bar/ResourceUsageStatusSegment.tsx | 21 ++++++++-- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/src/renderer/src/components/shared/useDaemonActions.tsx b/src/renderer/src/components/shared/useDaemonActions.tsx index b8dd7c91483..0dab5157e87 100644 --- a/src/renderer/src/components/shared/useDaemonActions.tsx +++ b/src/renderer/src/components/shared/useDaemonActions.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react' +import React, { useCallback, useEffect, useRef, useState } from 'react' import { LoaderCircle } from 'lucide-react' import { toast } from 'sonner' import { Button } from '../ui/button' @@ -39,6 +39,22 @@ export type DaemonActionsApi = { export function useDaemonActions(callbacks?: DaemonActionCallbacks): DaemonActionsApi { const [pending, setPending] = useState(null) const [busyKind, setBusyKind] = useState(null) + const mountedRef = useRef(true) + + useEffect(() => { + mountedRef.current = true + return () => { + mountedRef.current = false + } + }, []) + + const clearPendingAction = useCallback((): void => { + if (!mountedRef.current) { + return + } + setBusyKind(null) + setPending(null) + }, []) const runRestart = useCallback(async () => { setBusyKind('restart') @@ -54,11 +70,12 @@ export function useDaemonActions(callbacks?: DaemonActionCallbacks): DaemonActio description: err instanceof Error ? err.message : undefined }) } finally { - setBusyKind(null) - setPending(null) - callbacks?.onRestartSettled?.() + clearPendingAction() + if (mountedRef.current) { + callbacks?.onRestartSettled?.() + } } - }, [callbacks]) + }, [callbacks, clearPendingAction]) const runKillAll = useCallback(async () => { setBusyKind('killAll') @@ -77,16 +94,19 @@ export function useDaemonActions(callbacks?: DaemonActionCallbacks): DaemonActio toast.error(`${remainingCount} session${remainingCount === 1 ? '' : 's'} refused to exit.`) } } catch (err) { - callbacks?.onKillAllError?.() + if (mountedRef.current) { + callbacks?.onKillAllError?.() + } toast.error('Couldn’t kill sessions.', { description: err instanceof Error ? err.message : undefined }) } finally { - setBusyKind(null) - setPending(null) - callbacks?.onKillAllSettled?.() + clearPendingAction() + if (mountedRef.current) { + callbacks?.onKillAllSettled?.() + } } - }, [callbacks]) + }, [callbacks, clearPendingAction]) const runConfirmed = useCallback(() => { if (pending === 'restart') { diff --git a/src/renderer/src/components/status-bar/ResourceUsageStatusSegment.tsx b/src/renderer/src/components/status-bar/ResourceUsageStatusSegment.tsx index a91ec7eb9f2..a79869011fd 100644 --- a/src/renderer/src/components/status-bar/ResourceUsageStatusSegment.tsx +++ b/src/renderer/src/components/status-bar/ResourceUsageStatusSegment.tsx @@ -695,6 +695,7 @@ export function ResourceUsageStatusSegment({ // somewhere stable for keyboard users. const popoverBodyRef = useRef(null) const popoverBodyFocusFrameRef = useRef(null) + const mountedRef = useRef(true) const cancelPopoverBodyFocusFrame = useCallback((): void => { if (popoverBodyFocusFrameRef.current === null) { @@ -706,6 +707,13 @@ export function ResourceUsageStatusSegment({ useEffect(() => cancelPopoverBodyFocusFrame, [cancelPopoverBodyFocusFrame]) + useEffect(() => { + mountedRef.current = true + return () => { + mountedRef.current = false + } + }, []) + const setPopoverBodyNode = useCallback( (node: HTMLDivElement | null): void => { // Why: the queued post-kill focus is only valid while the popover body exists. @@ -719,16 +727,23 @@ export function ResourceUsageStatusSegment({ const refreshSessions = useCallback(async () => { if (runtimeEnvironmentActive) { - setSessions([]) - setSessionsError(false) + if (mountedRef.current) { + setSessions([]) + setSessionsError(false) + } return } try { const result = await window.api.pty.listSessions() + if (!mountedRef.current) { + return + } setSessions(result) setSessionsError(false) } catch { - setSessionsError(true) + if (mountedRef.current) { + setSessionsError(true) + } } }, [runtimeEnvironmentActive])