@trigger.dev/sdk 4.5.0-rc.6 → 4.5.0-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/dist/commonjs/v3/ai.d.ts +171 -5
  2. package/dist/commonjs/v3/ai.js +309 -22
  3. package/dist/commonjs/v3/ai.js.map +1 -1
  4. package/dist/commonjs/v3/chat-server.d.ts +8 -0
  5. package/dist/commonjs/v3/chat-server.js +32 -10
  6. package/dist/commonjs/v3/chat-server.js.map +1 -1
  7. package/dist/commonjs/v3/chat-server.test.js +51 -0
  8. package/dist/commonjs/v3/chat-server.test.js.map +1 -1
  9. package/dist/commonjs/v3/createStartSessionAction.test.js +30 -0
  10. package/dist/commonjs/v3/createStartSessionAction.test.js.map +1 -1
  11. package/dist/commonjs/v3/sessions.d.ts +3 -2
  12. package/dist/commonjs/v3/sessions.js +3 -2
  13. package/dist/commonjs/v3/sessions.js.map +1 -1
  14. package/dist/commonjs/version.js +1 -1
  15. package/dist/esm/v3/ai.d.ts +171 -5
  16. package/dist/esm/v3/ai.js +309 -22
  17. package/dist/esm/v3/ai.js.map +1 -1
  18. package/dist/esm/v3/chat-server.d.ts +8 -0
  19. package/dist/esm/v3/chat-server.js +32 -10
  20. package/dist/esm/v3/chat-server.js.map +1 -1
  21. package/dist/esm/v3/chat-server.test.js +51 -0
  22. package/dist/esm/v3/chat-server.test.js.map +1 -1
  23. package/dist/esm/v3/createStartSessionAction.test.js +30 -0
  24. package/dist/esm/v3/createStartSessionAction.test.js.map +1 -1
  25. package/dist/esm/v3/sessions.d.ts +3 -2
  26. package/dist/esm/v3/sessions.js +3 -2
  27. package/dist/esm/v3/sessions.js.map +1 -1
  28. package/dist/esm/version.js +1 -1
  29. package/docs/ai/prompts.mdx +430 -0
  30. package/docs/ai-chat/actions.mdx +115 -0
  31. package/docs/ai-chat/anatomy.mdx +71 -0
  32. package/docs/ai-chat/backend.mdx +817 -0
  33. package/docs/ai-chat/background-injection.mdx +221 -0
  34. package/docs/ai-chat/changelog.mdx +850 -0
  35. package/docs/ai-chat/chat-local.mdx +174 -0
  36. package/docs/ai-chat/client-protocol.mdx +1081 -0
  37. package/docs/ai-chat/compaction.mdx +411 -0
  38. package/docs/ai-chat/custom-agents.mdx +364 -0
  39. package/docs/ai-chat/error-handling.mdx +415 -0
  40. package/docs/ai-chat/fast-starts.mdx +672 -0
  41. package/docs/ai-chat/frontend.mdx +580 -0
  42. package/docs/ai-chat/how-it-works.mdx +230 -0
  43. package/docs/ai-chat/lifecycle-hooks.mdx +530 -0
  44. package/docs/ai-chat/mcp.mdx +101 -0
  45. package/docs/ai-chat/overview.mdx +90 -0
  46. package/docs/ai-chat/patterns/branching-conversations.mdx +284 -0
  47. package/docs/ai-chat/patterns/code-sandbox.mdx +126 -0
  48. package/docs/ai-chat/patterns/database-persistence.mdx +414 -0
  49. package/docs/ai-chat/patterns/human-in-the-loop.mdx +275 -0
  50. package/docs/ai-chat/patterns/large-payloads.mdx +169 -0
  51. package/docs/ai-chat/patterns/oom-resilience.mdx +120 -0
  52. package/docs/ai-chat/patterns/persistence-and-replay.mdx +211 -0
  53. package/docs/ai-chat/patterns/recovery-boot.mdx +230 -0
  54. package/docs/ai-chat/patterns/skills.mdx +221 -0
  55. package/docs/ai-chat/patterns/sub-agents.mdx +383 -0
  56. package/docs/ai-chat/patterns/tool-result-auditing.mdx +148 -0
  57. package/docs/ai-chat/patterns/trusted-edge-signals.mdx +337 -0
  58. package/docs/ai-chat/patterns/version-upgrades.mdx +172 -0
  59. package/docs/ai-chat/pending-messages.mdx +343 -0
  60. package/docs/ai-chat/prompt-caching.mdx +206 -0
  61. package/docs/ai-chat/quick-start.mdx +161 -0
  62. package/docs/ai-chat/reference.mdx +909 -0
  63. package/docs/ai-chat/server-chat.mdx +263 -0
  64. package/docs/ai-chat/sessions.mdx +333 -0
  65. package/docs/ai-chat/testing.mdx +682 -0
  66. package/docs/ai-chat/tools.mdx +191 -0
  67. package/docs/ai-chat/types.mdx +242 -0
  68. package/docs/ai-chat/upgrade-guide.mdx +515 -0
  69. package/docs/apikeys.mdx +54 -0
  70. package/docs/building-with-ai.mdx +261 -0
  71. package/docs/bulk-actions.mdx +49 -0
  72. package/docs/changelog.mdx +6 -0
  73. package/docs/cli-deploy-commands.mdx +9 -0
  74. package/docs/cli-dev-commands.mdx +9 -0
  75. package/docs/cli-dev.mdx +8 -0
  76. package/docs/cli-init-commands.mdx +58 -0
  77. package/docs/cli-introduction.mdx +25 -0
  78. package/docs/cli-list-profiles-commands.mdx +42 -0
  79. package/docs/cli-login-commands.mdx +33 -0
  80. package/docs/cli-logout-commands.mdx +33 -0
  81. package/docs/cli-preview-archive.mdx +59 -0
  82. package/docs/cli-promote-commands.mdx +9 -0
  83. package/docs/cli-switch.mdx +43 -0
  84. package/docs/cli-update-commands.mdx +42 -0
  85. package/docs/cli-whoami-commands.mdx +33 -0
  86. package/docs/community.mdx +6 -0
  87. package/docs/config/config-file.mdx +602 -0
  88. package/docs/config/extensions/additionalFiles.mdx +38 -0
  89. package/docs/config/extensions/additionalPackages.mdx +40 -0
  90. package/docs/config/extensions/aptGet.mdx +34 -0
  91. package/docs/config/extensions/audioWaveform.mdx +20 -0
  92. package/docs/config/extensions/custom.mdx +380 -0
  93. package/docs/config/extensions/emitDecoratorMetadata.mdx +29 -0
  94. package/docs/config/extensions/esbuildPlugin.mdx +31 -0
  95. package/docs/config/extensions/ffmpeg.mdx +45 -0
  96. package/docs/config/extensions/lightpanda.mdx +56 -0
  97. package/docs/config/extensions/overview.mdx +67 -0
  98. package/docs/config/extensions/playwright.mdx +195 -0
  99. package/docs/config/extensions/prismaExtension.mdx +1014 -0
  100. package/docs/config/extensions/puppeteer.mdx +30 -0
  101. package/docs/config/extensions/pythonExtension.mdx +182 -0
  102. package/docs/config/extensions/syncEnvVars.mdx +291 -0
  103. package/docs/context.mdx +235 -0
  104. package/docs/database-connections.mdx +213 -0
  105. package/docs/deploy-environment-variables.mdx +435 -0
  106. package/docs/deployment/atomic-deployment.mdx +172 -0
  107. package/docs/deployment/overview.mdx +257 -0
  108. package/docs/deployment/preview-branches.mdx +224 -0
  109. package/docs/errors-retrying.mdx +379 -0
  110. package/docs/github-actions.mdx +222 -0
  111. package/docs/github-integration.mdx +136 -0
  112. package/docs/github-repo.mdx +8 -0
  113. package/docs/help-email.mdx +6 -0
  114. package/docs/help-slack.mdx +11 -0
  115. package/docs/hidden-tasks.mdx +56 -0
  116. package/docs/how-it-works.mdx +454 -0
  117. package/docs/how-to-reduce-your-spend.mdx +217 -0
  118. package/docs/idempotency.mdx +504 -0
  119. package/docs/introduction.mdx +223 -0
  120. package/docs/limits.mdx +241 -0
  121. package/docs/logging.mdx +195 -0
  122. package/docs/machines.mdx +952 -0
  123. package/docs/manual-setup.mdx +632 -0
  124. package/docs/mcp-agent-rules.mdx +41 -0
  125. package/docs/mcp-introduction.mdx +385 -0
  126. package/docs/mcp-tools.mdx +273 -0
  127. package/docs/migrating-from-v3.mdx +334 -0
  128. package/docs/observability/dashboards.mdx +102 -0
  129. package/docs/observability/query.mdx +585 -0
  130. package/docs/open-source-contributing.mdx +16 -0
  131. package/docs/open-source-self-hosting.mdx +541 -0
  132. package/docs/private-networking/aws-console-setup.mdx +304 -0
  133. package/docs/private-networking/overview.mdx +144 -0
  134. package/docs/private-networking/troubleshooting.mdx +78 -0
  135. package/docs/queue-concurrency.mdx +354 -0
  136. package/docs/quick-start.mdx +97 -0
  137. package/docs/realtime/auth.mdx +208 -0
  138. package/docs/realtime/backend/overview.mdx +45 -0
  139. package/docs/realtime/backend/streams.mdx +418 -0
  140. package/docs/realtime/backend/subscribe.mdx +225 -0
  141. package/docs/realtime/how-it-works.mdx +94 -0
  142. package/docs/realtime/overview.mdx +63 -0
  143. package/docs/realtime/react-hooks/overview.mdx +73 -0
  144. package/docs/realtime/react-hooks/streams.mdx +449 -0
  145. package/docs/realtime/react-hooks/subscribe.mdx +674 -0
  146. package/docs/realtime/react-hooks/swr.mdx +87 -0
  147. package/docs/realtime/react-hooks/triggering.mdx +194 -0
  148. package/docs/realtime/react-hooks/use-wait-token.mdx +34 -0
  149. package/docs/realtime/run-object.mdx +174 -0
  150. package/docs/replaying.mdx +72 -0
  151. package/docs/request-feature.mdx +6 -0
  152. package/docs/roadmap.mdx +6 -0
  153. package/docs/run-tests.mdx +20 -0
  154. package/docs/run-usage.mdx +113 -0
  155. package/docs/runs/heartbeats.mdx +38 -0
  156. package/docs/runs/max-duration.mdx +139 -0
  157. package/docs/runs/metadata.mdx +734 -0
  158. package/docs/runs/priority.mdx +31 -0
  159. package/docs/runs.mdx +396 -0
  160. package/docs/self-hosting/docker.mdx +458 -0
  161. package/docs/self-hosting/env/supervisor.mdx +74 -0
  162. package/docs/self-hosting/env/webapp.mdx +276 -0
  163. package/docs/self-hosting/kubernetes.mdx +601 -0
  164. package/docs/self-hosting/overview.mdx +108 -0
  165. package/docs/skills.mdx +85 -0
  166. package/docs/tags.mdx +120 -0
  167. package/docs/tasks/overview.mdx +697 -0
  168. package/docs/tasks/scheduled.mdx +382 -0
  169. package/docs/tasks/schemaTask.mdx +413 -0
  170. package/docs/tasks/streams.mdx +884 -0
  171. package/docs/triggering.mdx +1320 -0
  172. package/docs/troubleshooting-alerts.mdx +385 -0
  173. package/docs/troubleshooting-debugging-in-vscode.mdx +8 -0
  174. package/docs/troubleshooting-github-issues.mdx +6 -0
  175. package/docs/troubleshooting-uptime-status.mdx +6 -0
  176. package/docs/troubleshooting.mdx +398 -0
  177. package/docs/upgrading-packages.mdx +80 -0
  178. package/docs/vercel-integration.mdx +207 -0
  179. package/docs/versioning.mdx +56 -0
  180. package/docs/video-walkthrough.mdx +23 -0
  181. package/docs/wait-for-token.mdx +540 -0
  182. package/docs/wait-for.mdx +42 -0
  183. package/docs/wait-until.mdx +53 -0
  184. package/docs/wait.mdx +18 -0
  185. package/docs/writing-tasks-introduction.mdx +33 -0
  186. package/package.json +8 -5
  187. package/skills/trigger-authoring-chat-agent/SKILL.md +296 -0
  188. package/skills/trigger-authoring-tasks/SKILL.md +254 -0
  189. package/skills/trigger-chat-agent-advanced/SKILL.md +368 -0
  190. package/skills/trigger-cost-savings/SKILL.md +116 -0
  191. package/skills/trigger-realtime-and-frontend/SKILL.md +276 -0
@@ -0,0 +1,415 @@
1
+ ---
2
+ title: "Error handling"
3
+ sidebarTitle: "Error handling"
4
+ description: "How errors flow through chat.agent — stream errors, hook errors, run failures — and how to recover."
5
+ ---
6
+
7
+ import RcBanner from "/snippets/ai-chat-rc-banner.mdx";
8
+
9
+ <RcBanner />
10
+
11
+ `chat.agent` errors fall into four layers, each with different recovery semantics. The default behavior is **conversation-preserving**: a thrown error in a hook or `run()` does not kill the chat. The current turn ends with an error chunk, and the agent waits for the user's next message.
12
+
13
+ ## Error layers at a glance
14
+
15
+ | Layer | Source | Default behavior | Recovery |
16
+ |-------|--------|------------------|----------|
17
+ | **Stream** | `streamText` errors mid-response (rate limits, model API failures) | `onError` callback converts to error chunk | Sanitize message via `uiMessageStreamOptions.onError` |
18
+ | **Hook / turn** | Throws in `onValidateMessages`, `onTurnStart`, `run`, etc. | Error chunk + turn-complete written to stream; conversation continues | Catch in your hook, or rely on default |
19
+ | **Run** | Unhandled exception escapes the run | Run fails. No retry by default. Standard task `onFailure` fires. | `onFailure` task hook |
20
+ | **Frontend** | Stream delivers `{ type: "error", errorText }` | `useChat` exposes via `error` field and `onError` callback | Show toast, retry button, etc. |
21
+
22
+ ## Stream errors mid-turn
23
+
24
+ When the model API errors mid-response (rate limits, network failures, malformed output), the AI SDK's `streamText` calls the `onError` callback. Use `uiMessageStreamOptions.onError` to convert the error to a user-friendly string. The string is sent to the frontend as an error chunk.
25
+
26
+ ```ts
27
+ import { chat } from "@trigger.dev/sdk/ai";
28
+
29
+ export const myChat = chat.agent({
30
+ id: "my-chat",
31
+ uiMessageStreamOptions: {
32
+ onError: (error) => {
33
+ console.error("Stream error:", error);
34
+ if (error instanceof Error && error.message.includes("rate limit")) {
35
+ return "Rate limited. Please wait a moment and try again.";
36
+ }
37
+ if (error instanceof Error && error.message.includes("context_length")) {
38
+ return "This conversation is too long. Please start a new chat.";
39
+ }
40
+ return "Something went wrong while generating a response. Please try again.";
41
+ },
42
+ },
43
+ run: async ({ messages, signal }) => {
44
+ return streamText({ model: anthropic("claude-sonnet-4-5"), messages, abortSignal: signal });
45
+ },
46
+ });
47
+ ```
48
+
49
+ <Note>
50
+ Returning a string from `onError` is what gets shown to the user. Do not return raw error messages — they may leak internal details (API keys, stack traces, etc.).
51
+ </Note>
52
+
53
+ The frontend receives this as an error chunk that `useChat` exposes via its `error` field:
54
+
55
+ ```tsx
56
+ const { messages, error } = useChat({ transport });
57
+
58
+ {error && <div className="text-red-600">{error.message}</div>}
59
+ ```
60
+
61
+ ## Hook and turn errors
62
+
63
+ If any lifecycle hook (`onValidateMessages`, `onChatStart`, `onTurnStart`, `hydrateMessages`, `onAction`, `prepareMessages`, `onBeforeTurnComplete`, `onTurnComplete`) or `run()` throws an unhandled exception, the turn loop catches it:
64
+
65
+ 1. Writes `{ type: "error", errorText: error.message }` to the stream
66
+ 2. Writes a turn-complete chunk to close the turn
67
+ 3. Waits for the next user message
68
+
69
+ The conversation stays alive. The user can send another message and continue.
70
+
71
+ ```ts
72
+ export const myChat = chat.agent({
73
+ id: "my-chat",
74
+ onTurnStart: async ({ chatId, uiMessages }) => {
75
+ // If this throws, the turn ends with an error chunk
76
+ // and the agent waits for the next message
77
+ await db.chat.update({ where: { id: chatId }, data: { messages: uiMessages } });
78
+ },
79
+ run: async ({ messages, signal }) => {
80
+ return streamText({ model: anthropic("claude-sonnet-4-5"), messages, abortSignal: signal });
81
+ },
82
+ });
83
+ ```
84
+
85
+ ### Catching errors in your own hooks
86
+
87
+ For granular control, wrap your hook code in try/catch and decide what to do. Common patterns:
88
+
89
+ ```ts
90
+ onValidateMessages: async ({ messages }) => {
91
+ try {
92
+ return await validateUIMessages({ messages, tools: chatTools });
93
+ } catch (err) {
94
+ // Log to your error tracking service
95
+ Sentry.captureException(err);
96
+ // Throw a user-facing error message — this becomes the error chunk
97
+ throw new Error("Your message contains invalid data and could not be sent.");
98
+ }
99
+ },
100
+ ```
101
+
102
+ <Tip>
103
+ The `Error.message` you throw is sent verbatim to the frontend as the error chunk's `errorText`. Use messages safe for end users.
104
+ </Tip>
105
+
106
+ ### Catching errors inside `run()`
107
+
108
+ `run()` is your code — wrap it in try/catch for full control. This is the right place to save partial state to your DB before the error chunk goes out:
109
+
110
+ ```ts
111
+ run: async ({ messages, chatId, signal }) => {
112
+ try {
113
+ return streamText({ model: anthropic("claude-sonnet-4-5"), messages, abortSignal: signal });
114
+ } catch (err) {
115
+ // Save the failed turn for debugging / undo
116
+ await db.failedTurn.create({
117
+ data: {
118
+ chatId,
119
+ error: err instanceof Error ? err.message : String(err),
120
+ messages,
121
+ },
122
+ });
123
+ throw err; // Re-throw to trigger the error chunk
124
+ }
125
+ },
126
+ ```
127
+
128
+ ## Saving error state to your DB
129
+
130
+ To persist errors for debugging or undo, use `onTurnComplete` (which fires even after errors) or the standard task `onComplete` hook.
131
+
132
+ ### Using `onTurnComplete`
133
+
134
+ `onTurnComplete` fires after every turn — successful **or** errored. On an errored turn `responseMessage` is undefined or partial and `error` carries the thrown value (with `finishReason` set to `"error"`). Use this to mark the turn as failed:
135
+
136
+ ```ts
137
+ onTurnComplete: async ({ chatId, uiMessages, responseMessage, stopped, error }) => {
138
+ // Persist the messages regardless of error state
139
+ await db.chat.update({
140
+ where: { id: chatId },
141
+ data: {
142
+ messages: uiMessages,
143
+ // `error` is set when the turn threw
144
+ lastTurnStatus: error ? "errored" : stopped ? "stopped" : "ok",
145
+ },
146
+ });
147
+ },
148
+ ```
149
+
150
+ ### Using the standard `onFailure` task hook
151
+
152
+ For run-level failures (the entire run dies), use the standard task `onFailure` hook. This fires when the run terminates with an unhandled exception:
153
+
154
+ ```ts
155
+ chat.agent({
156
+ id: "my-chat",
157
+ onFailure: async ({ error, ctx }) => {
158
+ // Log run-level failure to your monitoring service
159
+ await monitoring.recordRunFailure({
160
+ runId: ctx.run.id,
161
+ chatId: ctx.run.tags.find(t => t.startsWith("chat:"))?.slice(5),
162
+ error: error.message,
163
+ });
164
+ },
165
+ run: async ({ messages, signal }) => {
166
+ return streamText({ ... });
167
+ },
168
+ });
169
+ ```
170
+
171
+ <Info>
172
+ `chat.agent` uses `retry: { maxAttempts: 1 }` internally, so the run never retries on failure. To add run-level retries, wrap the agent in a parent task or implement your own retry logic in the frontend (re-send the message).
173
+ </Info>
174
+
175
+ ## Recovery patterns
176
+
177
+ ### Pattern 1: Undo to last successful response
178
+
179
+ A common pattern is to let the user "undo" the failed turn and try again. Combine `chat.history.rollbackTo` with a custom action:
180
+
181
+ ```ts
182
+ chat.agent({
183
+ id: "my-chat",
184
+ actionSchema: z.discriminatedUnion("type", [
185
+ z.object({ type: z.literal("undo") }),
186
+ ]),
187
+ onAction: async ({ action, uiMessages }) => {
188
+ if (action.type === "undo") {
189
+ // Find the last user message and roll back to it
190
+ const lastUserIdx = [...uiMessages].reverse().findIndex(m => m.role === "user");
191
+ if (lastUserIdx !== -1) {
192
+ const targetIdx = uiMessages.length - 1 - lastUserIdx - 1;
193
+ const target = uiMessages[targetIdx];
194
+ if (target) chat.history.rollbackTo(target.id);
195
+ }
196
+ }
197
+ },
198
+ run: async ({ messages, signal }) => {
199
+ return streamText({ ... });
200
+ },
201
+ });
202
+ ```
203
+
204
+ On the frontend, show an "Undo" button when an error occurs:
205
+
206
+ ```tsx
207
+ {error && (
208
+ <button onClick={() => transport.sendAction(chatId, { type: "undo" })}>
209
+ Undo and try again
210
+ </button>
211
+ )}
212
+ ```
213
+
214
+ ### Pattern 2: Retry the last message
215
+
216
+ For transient errors (network blips, rate limits), the simplest recovery is to re-send the last user message. The AI SDK's `useChat` provides `regenerate()`:
217
+
218
+ ```tsx
219
+ const { messages, error, regenerate } = useChat({ transport });
220
+
221
+ {error && (
222
+ <button onClick={() => regenerate()}>Retry</button>
223
+ )}
224
+ ```
225
+
226
+ `regenerate()` removes the last assistant response and re-sends. Combined with `onValidateMessages` or `hydrateMessages`, you can reload the canonical state from your DB before retrying.
227
+
228
+ ### Pattern 3: Save partial responses
229
+
230
+ When a stream errors mid-response, the `responseMessage` in `onBeforeTurnComplete` and `onTurnComplete` contains the partial output. Save it as a "draft" so the user can see what was generated before the error:
231
+
232
+ ```ts
233
+ onBeforeTurnComplete: async ({ chatId, responseMessage, stopped }) => {
234
+ if (responseMessage && responseMessage.parts.length > 0) {
235
+ // Save partial response — user can manually accept or discard
236
+ await db.partialResponse.create({
237
+ data: {
238
+ chatId,
239
+ message: responseMessage,
240
+ reason: stopped ? "stopped" : "errored",
241
+ },
242
+ });
243
+ }
244
+ },
245
+ ```
246
+
247
+ ### Pattern 4: Fall back to a different model
248
+
249
+ If the primary model errors, try a fallback model in the same turn:
250
+
251
+ ```ts
252
+ run: async ({ messages, signal }) => {
253
+ try {
254
+ return streamText({
255
+ model: anthropic("claude-sonnet-4-5"),
256
+ messages,
257
+ abortSignal: signal,
258
+ stopWhen: stepCountIs(15),
259
+ });
260
+ } catch (err) {
261
+ console.warn("Primary model failed, falling back:", err);
262
+ return streamText({
263
+ model: anthropic("claude-sonnet-4-6"),
264
+ messages,
265
+ abortSignal: signal,
266
+ stopWhen: stepCountIs(15),
267
+ });
268
+ }
269
+ },
270
+ ```
271
+
272
+ <Note>
273
+ This only catches errors thrown synchronously by `streamText` setup. Errors that happen mid-stream go through `uiMessageStreamOptions.onError`, not your try/catch.
274
+ </Note>
275
+
276
+ ## What gets written to the stream on error
277
+
278
+ When an error occurs at any layer, the frontend's `UIMessageChunk` stream surfaces an error chunk:
279
+
280
+ ```json
281
+ { "type": "error", "errorText": "Rate limited. Please wait a moment and try again." }
282
+ ```
283
+
284
+ A `turn-complete` control record follows on `session.out` (header-form, not a data chunk — see [`turn-complete` control record](/ai-chat/client-protocol#turn-complete-control-record) for the wire format) to mark the turn as done.
285
+
286
+ The AI SDK's `useChat` processes this and:
287
+
288
+ 1. Sets `useChat`'s `error` field to an `Error` with `message = errorText`
289
+ 2. Calls the user's `onError` callback (if set)
290
+ 3. Marks the turn as complete (`status` returns to `"ready"`)
291
+
292
+ ```tsx
293
+ const { messages, error, status } = useChat({
294
+ transport,
295
+ onError: (err) => {
296
+ toast.error(err.message);
297
+ },
298
+ });
299
+ ```
300
+
301
+ ## Frontend error handling
302
+
303
+ ### Showing the error to the user
304
+
305
+ ```tsx
306
+ function Chat() {
307
+ const transport = useTriggerChatTransport({
308
+ task: "my-chat",
309
+ accessToken: ({ chatId }) => mintChatAccessToken(chatId),
310
+ startSession: ({ chatId, clientData }) =>
311
+ startChatSession({ chatId, clientData }),
312
+ });
313
+ const { messages, error, sendMessage } = useChat({ transport });
314
+
315
+ return (
316
+ <div>
317
+ {messages.map(m => /* ... */)}
318
+ {error && (
319
+ <div className="rounded border border-red-300 bg-red-50 p-3">
320
+ <p className="text-red-700">{error.message}</p>
321
+ </div>
322
+ )}
323
+ <form onSubmit={(e) => { e.preventDefault(); sendMessage(/* ... */); }}>
324
+ {/* ... */}
325
+ </form>
326
+ </div>
327
+ );
328
+ }
329
+ ```
330
+
331
+ ### Distinguishing error types
332
+
333
+ The `errorText` is just a string, so distinguish error types via prefixes or codes:
334
+
335
+ ```ts
336
+ // Backend
337
+ uiMessageStreamOptions: {
338
+ onError: (error) => {
339
+ if (error.message.includes("rate limit")) return "RATE_LIMIT: Please wait and try again.";
340
+ if (error.message.includes("context_length")) return "CONTEXT_TOO_LONG: Start a new chat.";
341
+ return "UNKNOWN: Something went wrong.";
342
+ },
343
+ },
344
+ ```
345
+
346
+ ```tsx
347
+ // Frontend
348
+ {error?.message.startsWith("RATE_LIMIT") && <RateLimitNotice />}
349
+ {error?.message.startsWith("CONTEXT_TOO_LONG") && <NewChatPrompt />}
350
+ ```
351
+
352
+ <Tip>
353
+ For richer error structures, use [`chat.response.write()`](/ai-chat/backend#custom-data-parts) with a custom `data-error` part type. This lets you ship structured error metadata (codes, retry hints, etc.) instead of stringly-typed messages.
354
+ </Tip>
355
+
356
+ ### Errors from `accessToken` / `startSession`
357
+
358
+ If your `accessToken` or `startSession` callback throws (auth failure, DB write failure, network error), the rejection surfaces through `useChat`'s `error` state — same as a stream error. The transport doesn't retry the callback automatically; the customer is responsible for handling it.
359
+
360
+ ```tsx
361
+ const transport = useTriggerChatTransport({
362
+ task: "my-chat",
363
+ accessToken: async ({ chatId }) => {
364
+ try {
365
+ return await mintChatAccessToken(chatId);
366
+ } catch (err) {
367
+ // Customer's server action failed (e.g. user lost auth).
368
+ // Re-throw to surface as a useChat error, or return a sentinel
369
+ // your UI can detect and prompt re-auth.
370
+ throw new Error(`AUTH_REFRESH: ${err.message}`);
371
+ }
372
+ },
373
+ startSession: ({ chatId, clientData }) =>
374
+ startChatSession({ chatId, clientData }),
375
+ });
376
+ ```
377
+
378
+ `startSession` failures most commonly mean the customer's authorization layer rejected the request (no plan, quota exceeded, user not allowed to chat with this agent). The customer's server should produce a meaningful error message; the transport propagates it verbatim to `useChat`'s `error` state.
379
+
380
+ ## Run-level retries
381
+
382
+ `chat.agent` uses `retry: { maxAttempts: 1 }` — the run **never retries** on unhandled failure. This is intentional: each turn is conversation-preserving, so a true run failure is severe and shouldn't silently retry (which could send duplicate API calls or mutate state twice).
383
+
384
+ To add retry-like behavior:
385
+
386
+ - **Per-turn retries**: handle inside `run()` with try/catch and a fallback model
387
+ - **Per-message retries**: re-send from the frontend (call `sendMessage` or `regenerate` again)
388
+ - **Whole-run retries**: wrap `chat.agent` with a parent task that has `retry` configured, and call the agent's task internally
389
+
390
+ ## Best practices
391
+
392
+ 1. **Always set `uiMessageStreamOptions.onError`** to sanitize stream errors before they reach the user.
393
+ 2. **Persist messages in `onTurnStart`** so a mid-stream failure still leaves the user's message visible.
394
+ 3. **Use `onTurnComplete` to mark turn status** in your DB (`ok` / `errored` / `stopped`).
395
+ 4. **Don't throw raw errors with internal details** in hooks — catch, log, then throw a sanitized user-facing message.
396
+ 5. **Provide an undo or retry affordance** in the UI when errors occur.
397
+ 6. **Use `onFailure` for run-level monitoring** (Sentry, monitoring dashboards).
398
+ 7. **For known transient errors (rate limits, network)**, consider a fallback model inside `run()` instead of failing the turn.
399
+
400
+ ## `ChatChunkTooLargeError`
401
+
402
+ A specific run-failing error worth flagging on its own. Anything written through the chat output is one record on the underlying realtime stream, capped at ~1 MiB per record. A single chunk over the cap throws `ChatChunkTooLargeError` (named export from `@trigger.dev/sdk`). The most common trigger is a tool whose result object is large enough to overflow as one `tool-output-available` chunk.
403
+
404
+ The error carries `chunkType`, `chunkSize`, and `maxSize`. Catch with the `isChatChunkTooLargeError` guard and route oversized values out-of-band.
405
+
406
+ See [Large payloads in chat.agent](/ai-chat/patterns/large-payloads) for the ID-reference pattern that works around the cap, plus guidance on transient data parts and out-of-band logging.
407
+
408
+ ## See also
409
+
410
+ - [`uiMessageStreamOptions.onError`](/ai-chat/backend#error-handling-with-onerror) — stream error handler details
411
+ - [Custom actions](/ai-chat/actions) — implement undo/retry actions
412
+ - [`chat.history`](/ai-chat/backend#chat-history) — rollback to a previous message
413
+ - [Large payloads](/ai-chat/patterns/large-payloads) — handling the ~1 MiB per-chunk cap
414
+ - [Database persistence](/ai-chat/patterns/database-persistence) — saving conversation state
415
+ - [Standard task hooks](/tasks/overview) — `onFailure`, `onComplete`, `onWait`, etc.