Skip to content

Commit be0ca5f

Browse files
committed
Fix duplicate assistant content on inspector tab switch in AgentView
1 parent 35d7aeb commit be0ca5f

1 file changed

Lines changed: 23 additions & 3 deletions

File tree

apps/webapp/app/components/runs/v3/agent/AgentView.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,20 @@ function useAgentSessionMessages({
249249
[initialMessages]
250250
);
251251

252+
// The snapshot URL is re-signed by the loader on every navigation
253+
// (tab switches in the inspector pane re-run the session loader),
254+
// which would otherwise re-trigger the subscription effect below
255+
// and replay post-snapshot `.out` chunks on top of the messages we
256+
// already accumulated — duplicating any assistant content that
257+
// lives past `snapshot.lastOutEventId` (e.g., a canceled run whose
258+
// turn never completed). Hold the URL behind a ref and keep it
259+
// out of the effect's deps so the effect runs exactly once per
260+
// mount.
261+
const snapshotUrlRef = useRef(snapshotPresignedUrl);
262+
useEffect(() => {
263+
snapshotUrlRef.current = snapshotPresignedUrl;
264+
}, [snapshotPresignedUrl]);
265+
252266
// `pendingRef` is the authoritative, eagerly-updated message state:
253267
// chunks mutate this synchronously as they arrive. A throttled flush
254268
// copies it into React state so UI updates are capped at ~10x/sec.
@@ -309,9 +323,10 @@ function useAgentSessionMessages({
309323
* have never completed a turn).
310324
*/
311325
const loadSnapshot = async (): Promise<string | undefined> => {
312-
if (!snapshotPresignedUrl) return undefined;
326+
const url = snapshotUrlRef.current;
327+
if (!url) return undefined;
313328
try {
314-
const resp = await fetch(snapshotPresignedUrl, { signal: abort.signal });
329+
const resp = await fetch(url, { signal: abort.signal });
315330
if (!resp.ok) return undefined;
316331
const json = (await resp.json()) as unknown;
317332
const parsed = ChatSnapshotV1Schema.safeParse(json);
@@ -550,7 +565,12 @@ function useAgentSessionMessages({
550565
pendingTimerRef.current = null;
551566
}
552567
};
553-
}, [sessionId, apiOrigin, orgSlug, projectSlug, envSlug, snapshotPresignedUrl]);
568+
// `snapshotPresignedUrl` is intentionally NOT in this dep list — see
569+
// `snapshotUrlRef` above for the reasoning. Including it caused the
570+
// subscription to tear down + replay on every inspector tab click,
571+
// which appended duplicate parts to any assistant message whose
572+
// chunks lived past `snapshot.lastOutEventId`.
573+
}, [sessionId, apiOrigin, orgSlug, projectSlug, envSlug]);
554574

555575
return useMemo(() => {
556576
const timestamps = timestampsRef.current;

0 commit comments

Comments
 (0)