From dba87de0cbcdce34226602c1c6460ef186e61f1d Mon Sep 17 00:00:00 2001 From: "Birch (gastown)" Date: Thu, 2 Apr 2026 01:24:21 +0000 Subject: [PATCH] fix(admin): replace useEffect restart poll with useQuery pattern in KiloclawInstanceDetail Replace the useEffect-based polling (which modified the main data query's refetchInterval and used a useRef to track previous status) with a dedicated useQuery polling hook, matching the VolumeReassociatePanel pattern. The new approach: - Adds a separate 'machine-restart-poll' useQuery that invalidates the get query every 3 seconds while isRestartPending (awaitingRestartCompletion) - Uses a render-time check (not useEffect) to detect when the machine returns to 'running' and re-invalidate controllerVersion/gatewayStatus - Removes awaitingRestartCompletion from the main data query's refetchInterval This fixes the race condition where controllerVersion was refetched while the machine was still mid-restart, caching a stale value for the full 5-minute staleTime. --- .../KiloclawInstanceDetail.tsx | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/src/app/admin/components/KiloclawInstances/KiloclawInstanceDetail.tsx b/src/app/admin/components/KiloclawInstances/KiloclawInstanceDetail.tsx index d373fcab4..5ece46637 100644 --- a/src/app/admin/components/KiloclawInstances/KiloclawInstanceDetail.tsx +++ b/src/app/admin/components/KiloclawInstances/KiloclawInstanceDetail.tsx @@ -1152,7 +1152,7 @@ export function KiloclawInstanceDetail({ instanceId }: { instanceId: string }) { const { data, isLoading, error } = useQuery({ ...trpc.admin.kiloclawInstances.get.queryOptions({ id: instanceId }), - refetchInterval: awaitingRestartCompletion || awaitingRestoreCompletion ? 3000 : false, + refetchInterval: awaitingRestoreCompletion ? 3000 : false, }); const userId = data?.user_id; @@ -1262,39 +1262,37 @@ export function KiloclawInstanceDetail({ instanceId }: { instanceId: string }) { '2026.2.26' ); - // After a restart/upgrade, poll the machine status until it returns to "running", - // then invalidate controllerVersion so supportsConfigRestore reflects the new build. - const prevMachineStatus = useRef(data?.workerStatus?.status); - useEffect(() => { - const status = data?.workerStatus?.status; - const wasRestarting = prevMachineStatus.current !== 'running'; - prevMachineStatus.current = status; + // After a restart/upgrade, poll the machine's get query until it returns to "running", + // then re-invalidate controllerVersion so supportsConfigRestore reflects the new build. + useQuery({ + queryKey: ['machine-restart-poll', instanceId, awaitingRestartCompletion], + queryFn: async () => { + void queryClient.invalidateQueries({ + queryKey: trpc.admin.kiloclawInstances.get.queryKey(), + }); + return { ts: Date.now() }; + }, + enabled: awaitingRestartCompletion, + refetchInterval: awaitingRestartCompletion ? 3000 : false, + }); - if (awaitingRestartCompletion && status === 'running' && wasRestarting) { - setAwaitingRestartCompletion(false); - if (data?.user_id && data?.id) { - void queryClient.invalidateQueries({ - queryKey: trpc.admin.kiloclawInstances.controllerVersion.queryKey({ - userId: data.user_id, - instanceId: data.id, - }), - }); - void queryClient.invalidateQueries({ - queryKey: trpc.admin.kiloclawInstances.gatewayStatus.queryKey({ - userId: data.user_id, - instanceId: data.id, - }), - }); - } + if (awaitingRestartCompletion && data?.workerStatus?.status === 'running') { + setAwaitingRestartCompletion(false); + if (data.user_id && data.id) { + void queryClient.invalidateQueries({ + queryKey: trpc.admin.kiloclawInstances.controllerVersion.queryKey({ + userId: data.user_id, + instanceId: data.id, + }), + }); + void queryClient.invalidateQueries({ + queryKey: trpc.admin.kiloclawInstances.gatewayStatus.queryKey({ + userId: data.user_id, + instanceId: data.id, + }), + }); } - }, [ - data?.workerStatus?.status, - data?.user_id, - data?.id, - awaitingRestartCompletion, - queryClient, - trpc, - ]); + } // Stop polling when restore completes (status transitions from 'restoring' to something else). // Track whether we've seen 'restoring' to avoid false positives when the mutation succeeds