Skip to content

Hydration failure with persistence: true is silent, apps can't show a load error #1331

Description

@oztune

TanStack AI version

@tanstack/ai-react v0.23.0 (@tanstack/ai-client v0.30.0; verified unchanged in v0.31.0 and main)

Framework/Library version

React v19

Describe the bug and the steps to reproduce it

What happens

I'm using persistence: true, so the client loads an existing thread from my server when the chat mounts. When that load fails (my server returned a 500), the failure is invisible: onError doesn't fire, chat.error stays undefined, and the client reports status: 'ready' with zero messages, exactly what it reports for a thread that's genuinely empty. So my app can't tell "this thread failed to load" from "this thread has no messages", and the user is stuck on an empty/loading screen. There's no retry either; the load happens once on mount.

Steps to reproduce

No provider/API key needed, the failure happens before any send:

const chat = useChat({
	connection: fetchServerSentEvents('/nonexistent-endpoint'), // hydrate GET fails
	persistence: true,
	threadId: 'thread-1',
	onError: (e) => console.log('onError', e), // never logs
})
// Render chat.status + chat.error: status stays 'ready', error stays undefined,
// while the Network tab shows the failed GET.

What I expected

An error signal. GenerationClient.failHydration handles this exact case (status: 'error' + onError) with the comment "so a broken server is distinguishable from an empty one and the app can retry."

Cause

hydrateFromServer in chat-client.ts:

try {
	result = await hydrate(this.threadId)
} catch {
	return
}

No callback, state change, event, or retry is reachable from that catch. Same in 0.30.0, 0.31.0, and main.

Related breakage

For apps where users supply their own provider key (BYOK), a 401 during hydrate throws ByokMissingError into this same catch, so the key-request flow never triggers on thread load. The send path special-cases that error; the hydrate path eats it.

Fix

Minimal: catch (error) { this.callbacksRef.current.onError(error) }.
Fuller: port the failHydration pattern so state reflects it too.
(#1120 asks for hydration state generally, this may want to fold into that.)

Happy to PR either. Which shape do you prefer?

Your Minimal, Reproducible Example - (Sandbox Highly Recommended)

https://codesandbox.io/p/sandbox/pvgmmh?file=%2Findex.js

Screenshots or Videos (Optional)

No response

Do you intend to try to help solve this bug with your own PR?

Yes, I think I know how to fix it and will discuss it in the comments of this issue

Terms & Code of Conduct

  • I agree to follow this project's Code of Conduct
  • I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions