-
Notifications
You must be signed in to change notification settings - Fork 576
Description
Problem
When features are in progress with auto-mode running, the UI makes excessive HTTP requests to multiple endpoints nearly every second:
POST /api/worktree/list 200
POST /api/worktree/list 200
OPTIONS /api/features/get 204
POST /api/features/get 200
OPTIONS /api/features/agent-output 204
POST /api/features/agent-output 200
OPTIONS /api/worktree/list 204
OPTIONS /api/worktree/init-script 204
GET /api/worktree/init-script 200
Root Cause Analysis
The excessive requests are caused by multiple overlapping refresh mechanisms:
1. Agent Info Panel Polling (agent-info-panel.tsx:78-86)
Each feature card in progress has an AgentInfoPanel that polls both:
useFeatureevery 3 secondsuseAgentOutputevery 3 seconds
With N features in progress, this creates 2N API calls every 3 seconds.
2. Auto-Mode Progress Event Invalidation (use-query-invalidation.ts:80-84)
if (event.type === 'auto_mode_progress' && 'featureId' in event) {
queryClient.invalidateQueries({
queryKey: queryKeys.features.agentOutput(projectPath, event.featureId),
});
}When auto-mode is running, auto_mode_progress events are emitted frequently (likely with each agent output chunk). Each event invalidates the agentOutput query, triggering an immediate refetch - in addition to the 3-second polling.
3. Worktree Panel Polling (worktree-panel.tsx:189-199)
The worktree panel polls /api/worktree/list every 5 seconds via setInterval.
4. React StrictMode (dev only)
StrictMode double-invokes effects in development, potentially doubling some requests on component mount.
Proposed Solutions
Option 1: Debounce progress event invalidations (Recommended)
Instead of invalidating immediately on every auto_mode_progress event, debounce the invalidation:
// use-query-invalidation.ts
import { useMemo } from 'react';
import debounce from 'lodash.debounce';
// Debounce agent output invalidation to avoid excessive refetches
const debouncedInvalidateAgentOutput = useMemo(
() => debounce((featureId: string) => {
queryClient.invalidateQueries({
queryKey: queryKeys.features.agentOutput(projectPath, featureId),
});
}, 1000), // 1 second debounce
[queryClient, projectPath]
);
// Then in the event handler:
if (event.type === 'auto_mode_progress' && 'featureId' in event) {
debouncedInvalidateAgentOutput(event.featureId);
}Option 2: Disable polling when receiving events
If we're receiving WebSocket events for a feature, we don't need polling since the events drive invalidation. The AgentInfoPanel could detect this:
// Disable polling if we recently received a progress event
const shouldPoll = (isCurrentAutoTask || feature.status === 'in_progress') && !recentlyReceivedEvent;Option 3: Remove init-script polling trigger
The useWorktreeInitScript hook doesn't need to refetch when worktrees are polled. It should only fetch once on mount and when explicitly invalidated (after save/delete mutations).
Option 4: Batch invalidations
Collect all invalidations during a frame and batch them into a single update.
Impact
- Reduced server load
- Better client performance (fewer React re-renders)
- Reduced network traffic
- Improved battery life on laptops/mobile
Files to Modify
apps/ui/src/hooks/use-query-invalidation.ts- Debounce progress event invalidationsapps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx- Conditionally disable pollingapps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx- Consider if 5s polling is necessary