Skip to content

fix(assistant): stop cross-tab message sync from looping regenerations - #1441

Open
francoisauclair911 wants to merge 1 commit into
nuxt-content:mainfrom
francoisauclair911:fix/assistant-cross-tab-regenerate
Open

francoisauclair911 wants to merge 1 commit into
nuxt-content:mainfrom
francoisauclair911:fix/assistant-cross-tab-regenerate

Conversation

@francoisauclair911

@francoisauclair911 francoisauclair911 commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Problem

With the assistant open in two or more tabs of the same site, sending one question produces a pile of duplicated answers. The panel keeps restarting the tool calls and streaming, and the stored history fills with repeated assistant messages.

Cause:

  1. useAssistant persists messages with useLocalStorage('assistant-messages', []). By default this also listens to storage events, so every tab picks up the other tabs' writes.
  2. AssistantPanel watches those messages and calls regenerate() whenever the last one is a user message and the panel isn't streaming.
  3. When tab A sends a question, tab B receives it through the storage event, sees a trailing user turn and sends its own request.
  4. From then on, each tab's streaming and onFinish writes overwrite the other tab's messages, and the tabs keep triggering regenerations in each other.

Reproduction

Tested on main (dc05aff), using playground/ with assistant.enabled: true. The route was replaced with a mock createUIMessageStream that streams text for about 5s, so no LLM or MCP is involved. Headless Chromium, one browser context, one question sent from tab 0, history read 25s later:

POST /api/assistant stored assistant-messages roles
1 tab 1 user, assistant
2 tabs, before 2 (one per tab) user + 27 × assistant
2 tabs, after 1 user, assistant

After the fix, a reload still restores the conversation from localStorage.

Fix

Pass listenToStorageChanges: false to the messages useLocalStorage. Messages still persist across reloads, but each tab keeps its own conversation. Within a tab, useAssistant is a shared composable, so open(initialMessage) still reaches the panel without storage events.

Trade-off: a conversation no longer shows up live in other open tabs. It still loads on the next page load, as it did before. Live cross-tab mirroring would need the panel to tell a local user turn from one synced in from another tab. That seems out of scope for this bug fix, but I'm happy to explore it if you'd rather keep live sync.

eslint and nuxt typecheck layer pass.

`useAssistant` persists messages with `useLocalStorage`, which also syncs
them from other tabs via `storage` events. `AssistantPanel` regenerates
whenever synced messages end on a user turn, so with two tabs open each
tab restarts the other's stream and answers pile up in the history.

Keep persistence across reloads but stop listening to other tabs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 29, 2026

Copy link
Copy Markdown

@francoisauclair911 is attempting to deploy a commit to the NuxtLabs Team on Vercel.

A member of the Team first needs to authorize it.

@pkg-pr-new

pkg-pr-new Bot commented Sep 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/create-docus@1441
npm i https://pkg.pr.new/docus@1441

commit: debb11c

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant