TLDR: When your AI chat transport needs fresh context at send time, don't recreate the transport — keep it stable and read the latest value through a
useRef.
The Setup
We were building an AI chat widget for a practice management system (think: matters, contacts, case files — a busy legal/medical practice). The goal was smart context injection — when you're viewing Matter #1042, the chat agent actually knows that. It gets the matter name, the primary contact, the relevant notes, all piped in automatically.
Took about an hour to wire up. feat(chat): inject matter/contact context per page into AI agent — shipped, moved on.
Except.
The Wall
Navigate from one matter to a different one, open the chat, ask a question about the current case.
The agent answers confidently about the old one.
The transport — the AI SDK's pluggable send layer (the object that actually ferries your message to the model) — had captured the initial context in a closure when it was first created. It was holding a frozen snapshot of "what the page looked like when the chat first mounted." Classic stale closure. The matter changed. The transport didn't know.
What I Tried First
Obvious instinct: put the context in the deps array and recreate the transport when it changes.
This is the wrong move.
Every matter navigation would tear down the transport and spin up a new one. Reconnection overhead, potential for in-flight messages to evaporate, a subtle churn you can feel in the UX. And honestly — a transport is expensive to create. You don't want to recreate it every time a prop changes.
So recreating doesn't work. But stale closure is also unacceptable.
The Fix That Worked
Create the transport once. Read the context at send time.
The trick is a useRef. You store the live context in a ref — updated on every render with no re-creation — and inside the transport's send function, you read contextRef.current at the moment the user actually hits send.
const contextRef = useRef(pageContext);
useEffect(() => { contextRef.current = pageContext; });
// inside the transport (created once, stable):
const send = (message) => {
const ctx = contextRef.current; // always fresh
return callAgent({ message, context: ctx });
};
The transport never goes stale. It never restarts. It just reaches for the ref every time it needs to know what page you're on.
Commit message says it all: fix(chat): keep transport stable; read context via ref at send time.
Why This Matters to Me
This is the dilemma React throws at you constantly — stability vs. freshness — and it's never obvious until you've been burned. You can't fix stale closure by making things unstable. The ref is the escape hatch. It's not fancy. It's not new. But it's exactly the right tool for any long-lived object that needs to read state it didn't create.
If you're building AI chat into a real app, you WILL hit this. Now you know the move.