cloudflare / cloudflare/agents

useAgentChat still hits React #185 with a 50ms throttle on agents@0.22.0

Open
#2,267 0 comments 0 reactions 1 assignee Claimed by @threepointone View on GitHub
bug
Dominant language
TypeScript
Stars
5.6k
Forks
711
Avg merge
1d 20h
Merged PRs (30d)
53

Description

## Summary

`useAgentChat` can still enter `status: "error"` with React #185 (`Maximum update depth exceeded`) during a long streamed turn, even when chat updates are explicitly throttled at 50 ms.

This looks related to #1361 / #1732, but the occurrence described below is on `agents@0.22.0`, after the default-throttle work in #2058, with `experimental_throttle: 50` explicitly configured. The application also carries an equivalent of the proposed #2217 fix, so the `clientToolResults` no-op cleanup dispatch should not be the remaining source.

## Environment

- `agents@0.22.0`
- `ai@7.0.93`
- `@ai-sdk/react@4.0.96`
- `react@19.2.7`
- Mobile Safari
- Production build
- `useAgentChat({ resume: true, experimental_throttle: 50 })`

## Actual behavior

During a long streamed chat turn, the client intermittently throws React #185. The source-mapped stack reaches the Agents chat response handling / `setMessages` path, then the AI SDK `ReactChatState` subscriber callback and React's `forceStoreRerender`. The client stream is assigned an error even though this is a React-side update-depth failure.

## Why this appears distinct from #2217

The affected application has a pre-dispatch stale-entry check equivalent to the fix proposed in #2217. The `clientToolResults` cleanup effect does not call `setClientToolResults` unless a stale entry actually exists, so it no longer schedules a no-op update on every streamed `chatMessages` change.

## Suspected remaining update source

The fallback/observer branch still applies each live broadcast update directly to the AI SDK store:

```ts
const result = broadcastTransition(streamStateRef.current, {
type: "response",
streamId: data.id,
// ...
});
streamStateRef.current = result.state;

if (result.messagesUpdate) {
setMessages(result.messagesUpdate);
}
```

Current `main` still has this write:
https://github.com/cloudflare/agents/blob/main/packages/agents/src/chat/react.tsx#L2157-L2163

For a live observed stream, `broadcastTransition` can produce a `messagesUpdate` for every chunk. The configured `useChat` throttle limits React publication frequency, but it does not prevent these per-chunk `setMessages` calls from entering the store. This is a candidate rather than a proven root cause.

One possible mitigation would be to coalesce the latest accumulated observer update before calling `setMessages`, with an immediate flush for `done` / `replayComplete` and cancellation on clear/unmount.

## Reproduction status

We do not yet have a deterministic standalone reproduction. Stress attempts in Chromium with a mobile viewport did not reproduce it; the failure has only been observed intermittently in Mobile Safari so far. We can provide a sanitized source-mapped stack if useful.

## Expected behavior

Long and/or observed streamed turns should complete without exhausting React's nested-update budget or moving `useAgentChat` to `status: "error"`.

## Related

- #1361
- #1732
- #1913
- #2058
- #2217

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.