cloudflare / cloudflare/agents
useAgentChat: /get-messages fires with stale socket URL (and old auth token) when name prop changes
- Dominant language
- TypeScript
- Stars
- 5.6k
- Forks
- 711
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 53
Description
## Summary
When the `name` prop passed to `useAgent` changes (e.g. on logout → new guest session), `useAgentChat`'s initial messages fetch fires **during the same render** using the **old socket's URL** — which still contains the previous session's auth token. This causes two problems:
1. **Credential leak**: a signed-in user's auth token is sent to a new (guest) endpoint after logout.
2. **Data poisoning**: the old conversation's messages are cached under the new session's cache key. Because the key is already populated when the new socket's URL arrives, no corrective re-fetch occurs — the stale messages persist for the lifetime of the component.
## Root cause
In `packages/agents/src/chat/react.tsx`, `initialMessagesCacheKey` is prop-derived (`agentAddressKey`, set synchronously from `options.name` every render). When `name` changes:
1. `agentAddressKey` → new value (sync, this render)
2. `requestCache` miss → `doGetInitialMessages` fires **immediately**, with `agentUrlString` = `agent.getHttpUrl()` = the **old socket's `_url`** (still carries old conversationId in path and old `?token=...`)
3. `useStableSocket` replaces the socket in a `useEffect` — **after** this render — so `_url` is still stale when the fetch fires
The fetch URL and the cache key are therefore from different renders, violating the invariant that was safe in `@cloudflare/ai-chat@0.4.x` (where the cache key was derived from `agentUrl.pathname`, which lagged with the socket).
## Why the obvious fix (use `resolvedInitialMessagesCacheKey`) regresses #1356
`resolvedInitialMessagesCacheKey` (line 768) is the URL-inclusive key that would fix this, but it was deliberately excluded from `doGetInitialMessages` because including it causes a Suspense re-trigger on the null → resolved URL transition at first mount. The comment at lines 755–763 describes this exactly.
## Reproduction
1. Mount `useAgentChat` with `useAgent({ name: convA, query: { token: tokenA } })`
2. Change props to `{ name: convB, query: { token: tokenB } }`
3. Observe: `/get-messages` fires with the URL for `convA?token=tokenA`
4. The returned messages (from convA) are cached under convB's key
5. When the new socket arrives with convB's URL, no re-fetch occurs (cache hit on the poisoned entry)
6. Chat displays convA's messages under convB's session
## What would fix it
The two cases that need different behavior:
| Transition | Desired behavior |
|---|---|
| `agentUrl` null → resolved (first mount handshake) | Do NOT bust cache — existing behavior, avoids #1356 |
| `name` changes while `agentUrl` is already non-null (session switch) | DO use URL-inclusive key — wait for new socket URL before fetching |
A fix that distinguishes these two cases (e.g. tracking whether the previous `agentUrl.pathname` was non-null before the `name` change) would restore the 0.4.x invariant without reintroducing #1356.
## Workaround
Key the component mounting `useAgentChat` on `name` (force remount on session switch). This avoids the stale-fetch render entirely since the new mount's `agent.getHttpUrl()` is already correct.
## Versions
- `agents`: 0.17.3 (latest)
- `@cloudflare/ai-chat`: 0.9.3 (latest, now a re-export of `agents/chat/react`)
- Regression introduced when `useAgentChat` moved from `@cloudflare/ai-chat/dist/react.js` (0.4.x, inline) into `agents/src/chat/react.tsx`
Contributor guide
Assessment
This issue has not been assessed yet.