fix: resolve childAgents selector infinite loop in AgentScreenContent

The useShallow wrapper was not preventing infinite loops because the
selector created new object references on every store update. Fixed by:
1. Selecting the agents Map directly (stable reference)
2. Deriving childAgents array in useMemo with proper dependencies

The Map only changes when agents are added/removed, preventing
unnecessary re-renders that were causing the infinite loop.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2025-12-21 18:05:46 +00:00
co-authored by Claude Opus 4.5
parent 6cb0322dd7
commit c7294753ec
2 changed files with 16 additions and 17 deletions
@@ -1,5 +1,4 @@
import { useEffect, useMemo, useRef, useCallback, useState } from "react"; import { useEffect, useMemo, useRef, useCallback, useState } from "react";
import { useShallow } from "zustand/shallow";
import { import {
View, View,
Text, Text,
@@ -154,22 +153,21 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro
resolvedAgentId ? state.sessions[serverId]?.agents?.get(resolvedAgentId) : undefined resolvedAgentId ? state.sessions[serverId]?.agents?.get(resolvedAgentId) : undefined
); );
// Select child agents (agents where parentAgentId === current agentId) // Select the agents Map directly - this is a stable reference that only changes when agents are added/removed
// Use useShallow to avoid creating new array references on every render const allAgents = useSessionStore((state) => state.sessions[serverId]?.agents);
const childAgents = useSessionStore(
useShallow((state) => { // Derive child agents in useMemo to avoid creating new references on every store update
if (!resolvedAgentId) return []; // This prevents infinite loops caused by useShallow not doing deep equality on object arrays
const agents = state.sessions[serverId]?.agents; const childAgents = useMemo(() => {
if (!agents) return []; if (!resolvedAgentId || !allAgents) return [];
const children: Array<{ id: string; title: string | null }> = []; const children: Array<{ id: string; title: string | null }> = [];
for (const [id, a] of agents) { for (const [id, a] of allAgents) {
if (a.parentAgentId === resolvedAgentId) { if (a.parentAgentId === resolvedAgentId) {
children.push({ id, title: a.title }); children.push({ id, title: a.title });
}
} }
return children; }
}) return children;
); }, [allAgents, resolvedAgentId]);
// Select only the specific stream state - use stable empty array to avoid infinite loop // Select only the specific stream state - use stable empty array to avoid infinite loop
const streamItemsRaw = useSessionStore((state) => const streamItemsRaw = useSessionStore((state) =>
+2 -1
View File
@@ -402,12 +402,13 @@ Files requiring modification:
- Identify where the value is being lost - Identify where the value is being lost
- **Done (2025-12-21 18:15)**: CONFIRMED WORKING. Added debug logging to trace the full data flow. Testing revealed that `parentAgentId` IS being set correctly on ManagedAgent when created via MCP. The child agent `8f2959d3-2b64-41ca-9297-7473af713fb1` has `parentAgentId=24e6353c-24d9-4fc8-b3e0-e829adb000f5` both on the managed object and at the top-level of the stored record. The previous test failure was due to stale data from agents created before the storage fix was applied. Removed debug logging after confirming success. - **Done (2025-12-21 18:15)**: CONFIRMED WORKING. Added debug logging to trace the full data flow. Testing revealed that `parentAgentId` IS being set correctly on ManagedAgent when created via MCP. The child agent `8f2959d3-2b64-41ca-9297-7473af713fb1` has `parentAgentId=24e6353c-24d9-4fc8-b3e0-e829adb000f5` both on the managed object and at the top-level of the stored record. The previous test failure was due to stale data from agents created before the storage fix was applied. Removed debug logging after confirming success.
- [ ] **Fix**: Fix childAgents selector infinite loop in AgentScreenContent. - [x] **Fix**: Fix childAgents selector infinite loop in AgentScreenContent.
- The `useShallow` wrapper doesn't prevent infinite loops when selector returns new object references - The `useShallow` wrapper doesn't prevent infinite loops when selector returns new object references
- Consider extracting just agent IDs and using a separate lookup - Consider extracting just agent IDs and using a separate lookup
- Or use `useMemo` with proper dependency tracking - Or use `useMemo` with proper dependency tracking
- Ensure the selector returns stable references - Ensure the selector returns stable references
- **Done (2025-12-21 18:30)**: Fixed by replacing `useShallow` selector with a two-step approach: (1) select the agents Map directly (stable reference), (2) derive `childAgents` array in `useMemo` with proper dependencies. The Map only changes when agents are added/removed, preventing unnecessary re-renders. Removed unused `useShallow` import. Typecheck passes.
- [ ] **Plan**: Re-audit agent hierarchy after initial implementation. - [ ] **Plan**: Re-audit agent hierarchy after initial implementation.