Skip to content

Commit ba2b9cd

Browse files
committed
fix(web): break shell-stream loop that rewrote sidebar summary every tick
`writeShellStreamThread` compared the stored sidebar summary (which has a non-null computed `agentCommandStatus`) against `nextThread.summary` straight off the shell stream — but the server always sets `agentCommandStatus: null`. For any thread with an active URL detection the equality check could never succeed, so the block ran on every shell tick: it rebuilt the summary, recomputed the same status, and stored a new object reference even though nothing changed. That cascaded into unnecessary Zustand notifications and sidebar re-renders. Compute the desired summary first (via `withSidebarAgentCommandStatus`) and compare that to the stored one. When activities haven't changed the new check returns true and the no-op write is skipped, breaking the loop without changing the contract that activities own `agentCommandStatus` and `withSidebarAgentCommandStatus` derives it.
1 parent 4288cd6 commit ba2b9cd

1 file changed

Lines changed: 15 additions & 9 deletions

File tree

apps/web/src/store.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -808,24 +808,30 @@ function writeThreadShellState(
808808
};
809809
}
810810

811+
// The shell stream's `nextThread.summary` always carries `agentCommandStatus:
812+
// null` because the server doesn't compute it. We rebuild it from activities
813+
// BEFORE the equality check; otherwise the check sees the stored
814+
// (non-null computed) status vs. null and never returns true for threads
815+
// with active URL detections — that previously caused an unbounded loop of
816+
// no-op writes on every shell tick.
817+
const activityIds = nextState.activityIdsByThreadId[nextThread.shell.id] ?? EMPTY_ACTIVITY_IDS;
818+
const activitiesById = nextState.activityByThreadId[nextThread.shell.id] ?? {};
819+
const activities = activityIds.flatMap((id) => {
820+
const activity = activitiesById[id];
821+
return activity ? [activity] : [];
822+
});
823+
const desiredSummary = withSidebarAgentCommandStatus(nextThread.summary, activities);
811824
if (
812825
!sidebarThreadSummariesEqual(
813826
state.sidebarThreadSummaryById[nextThread.shell.id],
814-
nextThread.summary,
827+
desiredSummary,
815828
)
816829
) {
817-
const activityIds = nextState.activityIdsByThreadId[nextThread.shell.id] ?? EMPTY_ACTIVITY_IDS;
818-
const activitiesById = nextState.activityByThreadId[nextThread.shell.id] ?? {};
819-
const activities = activityIds.flatMap((id) => {
820-
const activity = activitiesById[id];
821-
return activity ? [activity] : [];
822-
});
823-
const summary = withSidebarAgentCommandStatus(nextThread.summary, activities);
824830
nextState = {
825831
...nextState,
826832
sidebarThreadSummaryById: {
827833
...nextState.sidebarThreadSummaryById,
828-
[nextThread.shell.id]: summary,
834+
[nextThread.shell.id]: desiredSummary,
829835
},
830836
};
831837
}

0 commit comments

Comments
 (0)