From c7294753ecb9b70c6df17b07bfdf9ea0a54cc67f Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sun, 21 Dec 2025 18:05:46 +0000 Subject: [PATCH] fix: resolve childAgents selector infinite loop in AgentScreenContent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../src/app/agent/[serverId]/[agentId].tsx | 30 +++++++++---------- plan.md | 3 +- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/app/src/app/agent/[serverId]/[agentId].tsx b/packages/app/src/app/agent/[serverId]/[agentId].tsx index 54af17062..77ec47dd0 100644 --- a/packages/app/src/app/agent/[serverId]/[agentId].tsx +++ b/packages/app/src/app/agent/[serverId]/[agentId].tsx @@ -1,5 +1,4 @@ import { useEffect, useMemo, useRef, useCallback, useState } from "react"; -import { useShallow } from "zustand/shallow"; import { View, Text, @@ -154,22 +153,21 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro resolvedAgentId ? state.sessions[serverId]?.agents?.get(resolvedAgentId) : undefined ); - // Select child agents (agents where parentAgentId === current agentId) - // Use useShallow to avoid creating new array references on every render - const childAgents = useSessionStore( - useShallow((state) => { - if (!resolvedAgentId) return []; - const agents = state.sessions[serverId]?.agents; - if (!agents) return []; - const children: Array<{ id: string; title: string | null }> = []; - for (const [id, a] of agents) { - if (a.parentAgentId === resolvedAgentId) { - children.push({ id, title: a.title }); - } + // Select the agents Map directly - this is a stable reference that only changes when agents are added/removed + const allAgents = useSessionStore((state) => state.sessions[serverId]?.agents); + + // Derive child agents in useMemo to avoid creating new references on every store update + // This prevents infinite loops caused by useShallow not doing deep equality on object arrays + const childAgents = useMemo(() => { + if (!resolvedAgentId || !allAgents) return []; + const children: Array<{ id: string; title: string | null }> = []; + for (const [id, a] of allAgents) { + if (a.parentAgentId === resolvedAgentId) { + 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 const streamItemsRaw = useSessionStore((state) => diff --git a/plan.md b/plan.md index 5d0c669b9..ca2f0e1a6 100644 --- a/plan.md +++ b/plan.md @@ -402,12 +402,13 @@ Files requiring modification: - 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. -- [ ] **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 - Consider extracting just agent IDs and using a separate lookup - Or use `useMemo` with proper dependency tracking - 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.