Symptom: the client shows a loading state forever. No error callback fires, no chunks arrive, the request eventually times out.

Cause candidates, in order of likelihood:
1. The stream was never closed or piped. An unclosed stream leaves the response open with zero bytes.
2. A response helper mismatch: toUIMessageStream output handed to a text-stream response helper (or vice versa), so the client parses nothing.
3. An exception inside onEnd or a lifecycle callback that got swallowed, killing the stream machinery mid-flight.
4. The provider call itself hung: no timeout set, no error surfaced, request open until the platform kills it.

Confirmation:
1. curl the endpoint with --no-buffer. Zero bytes and an open connection means the stream never produced output; an immediate connection close with no body means the response was malformed.
2. Check the route: result.stream must reach toUIMessageStream and then createUIMessageStreamResponse (or toTextStream then createTextStreamResponse). Any other pairing is suspect.
3. Wrap the route body in try/catch and log. If the log shows a rejection you never saw client-side, the error path is broken, not silent.

Fix:
- Use the matching helpers: UI message stream goes to createUIMessageStreamResponse; text stream goes to createTextStreamResponse.
- Keep lifecycle callbacks minimal and non-throwing. Log first, await second.
- Set a provider timeout and a route maxDuration so a hung provider call fails loudly instead of hanging the UI.

Verification: after the fix, curl shows chunks arriving immediately, and a forced provider error produces the onError message instead of silence.