This message failed to send.
ErrorChat - Error handling
Handle errors raised by adapters, streams, and history loading through a unified error model.
The chat runtime captures errors from adapters, streams, and history loading, and surfaces them through a unified error model. You don't need to catch errors inside adapter methods. The runtime handles them for you.
Interactive playground
The demo below lets you toggle a message error and observe the ChatMessageError component rendered under the failed message:
ChatMessageError
Inline error using palette.error tokens — rendered under failed messages.Error propagation
When an adapter method throws, the runtime:
- Records a
ChatErrorwith the appropriatesourceandcode. - Surfaces it through
ChatBox's built-in error UI,useChat().error, and theonErrorcallback. - Marks the error
recoverablewhen applicable (for example, stream disconnects) andretryablewhen the user can try again.
Both error surfaces are announced to assistive technology: the headless MessageError primitive renders with role="alert", and the Material ChatMessageError card uses aria-live="polite" with aria-atomic="true" so existing errors aren't re-announced on mount.
Handling errors at the application level
Use the onError prop on ChatBox to handle errors at the application level:
<ChatBox
adapter={adapter}
onError={(error) => {
console.error(`[Chat error] ${error.source}: ${error.message}`);
// Report to your error tracking service
errorTracker.capture(error);
}}
/>
The demo below shows the default error UI surfaced by ChatBox when sendMessage() fails:
Error object structure
Every error recorded by the runtime is represented as a ChatError:
interface ChatError {
code: ChatErrorCode;
message: string;
source: ChatErrorSource;
recoverable: boolean;
retryable?: boolean;
details?: Record<string, unknown>;
}
Error codes
| Code | Description |
|---|---|
SEND_ERROR |
The adapter's sendMessage() threw an error. |
STREAM_ERROR |
The stream failed or disconnected unexpectedly. |
HISTORY_ERROR |
Loading message history failed. |
REALTIME_ERROR |
The realtime subscription encountered an error. |
REGENERATE_ERROR |
Regenerating an assistant response failed. |
Error sources
| Source | Description |
|---|---|
'send' |
Error during message send. |
'stream' |
Error during stream processing. |
'history' |
Error during history loading. |
'render' |
Error during component rendering. |
'adapter' |
Generic adapter error. |
recoverable vs retryable
recoverable: the runtime can recover from this error automatically (for example, by reconnecting a dropped stream viareconnectToStream()); see Stream disconnect recovery.retryable: the user can try the operation again (for example, by re-sending a failed message).
Accessing the error state
The useChat() hook exposes the current error:
import { useChat } from '@mui/x-chat/headless';
function ErrorBanner() {
const { error, setError } = useChat();
if (!error) return null;
return (
<div role="alert">
<p>{error.message}</p>
<button onClick={() => setError(null)}>Dismiss</button>
</div>
);
}
Retrying failed messages
When sendMessage() fails, the user's message stays in the thread (optimistic update) and the composer re-enables so the user can try again.
The useChat() hook provides a retry method that re-sends the message associated with a given message ID:
import { useChat } from '@mui/x-chat/headless';
function RetryButton({ messageId }: { messageId: string }) {
const { retry } = useChat();
return <button onClick={() => retry(messageId)}>Retry</button>;
}
retry(messageId) looks up the original user message by ID, re-submits it through the adapter's sendMessage(), and replaces any previous error state.
retry(messageId) is a no-op while a send or stream is already in flight, and for messages whose role isn't 'user'. The built-in ChatMessageError retry button disables itself in those cases.
The demo below fails the first send attempt, then succeeds when you click Retry:
Error from adapter methods
You don't need to wrap adapter methods in try/catch to surface errors to the runtime, but you should wrap them to log to your observability platform.
If you want to transform or enrich an error before the runtime sees it, throw a plain Error with a custom message.
The runtime wraps it in a ChatError with source 'adapter':
async sendMessage({ message, signal }) {
try {
const res = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ message }),
signal,
});
if (!res.ok) {
throw new Error(`Server responded with ${res.status}`);
}
return res.body!;
} catch (error) {
// Log to Sentry, Datadog, or your error-tracking platform
console.error('Adapter sendMessage failed:', error);
// Re-throw so the runtime can surface the failure to the user
throw error;
}
},
Stream disconnect recovery
If a stream closes without a terminal chunk (finish or abort), the runtime:
- Records a recoverable stream error.
- Sets the message status to
'error'. - Calls
onFinishwithisDisconnect: true. - If
reconnectToStream()is implemented on the adapter, makes one attempt to resume the stream. - Calls
onErroronly when the disconnect remains unrecovered.
See Reconnecting to streams for details.
Message status and errors
The message status field reflects error states:
| Status | Description |
|---|---|
'pending' |
Message is queued but not yet dispatched to the adapter. |
'sending' |
Message is being sent (optimistic update). |
'streaming' |
Assistant response is streaming. |
'sent' |
Message was sent and response completed. |
'read' |
Message was delivered and marked as read (read receipts). |
'error' |
An error occurred during send or streaming. |
'cancelled' |
The stream was aborted by the user. |
Components can use the status field to conditionally render error indicators:
function MessageBubble({ message }: { message: ChatMessage }) {
return (
<div>
{message.parts.map((part) => /* render parts */)}
{message.status === 'error' && (
<span className="error-badge">Failed to send</span>
)}
</div>
);
}
Use the useMessageError(messageId) hook to read a single message's error from your own components.
See also
API
See the documentation below for a complete reference to all of the props and classes available to the components mentioned here.