Symptom: useChat's error state says "An error occurred" with no detail, while the server logs show the real exception.

Cause: secure-by-default masking. toUIMessageStream strips error details before they reach the client so provider messages, keys, and internals never leak to the browser.

Confirmation:
1. Reproduce, then check the server logs (not the browser). The real error (APICallError, missing key, bad model id) is logged server-side.
2. If the server log has the detailed error and the client shows the generic one, masking is working as designed. There is nothing to fix in the transport.

Fix: add an onError handler that returns a safe message for the client:

function errorHandler(error: unknown) {
  if (error instanceof Error) return error.message;
  return 'unknown error';
}

return createUIMessageStreamResponse({
  stream: toUIMessageStream({ stream: result.stream, onError: errorHandler }),
});

Rules:
1. Return a generic message in production ("Something went wrong"). Forwarding raw provider errors can leak account or quota details.
2. Log the full error server-side in the same handler before returning the safe string. You need the detail somewhere.
3. The same onError option exists for the older createDataStreamResponse path as toDataStreamResponse's onError. Use whichever matches your response helper.
4. Do not "fix" this by disabling masking globally. There is no supported off switch; onError is the intended seam.

Verification: trigger the failing case again and confirm the client shows your safe message while the server log carries the full error.