Skip to content

Commit 98dd8c7

Browse files
committed
fix(sdk): prevent uncaught errors thrown onSuccess, onComplete, and onFailure hooks to fail attempts & in some cases runs
1 parent 501a383 commit 98dd8c7

File tree

5 files changed

+244
-110
lines changed

5 files changed

+244
-110
lines changed

.changeset/tiny-carrots-rest.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
---
4+
5+
Prevent onSuccess, onComplete, and onFailure lifecycle hooks from failing attempts/runs

docs/tasks/overview.mdx

Lines changed: 122 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -174,63 +174,14 @@ tasks.onStart(({ ctx, payload, task }) => {
174174

175175
![Lifecycle functions](/images/lifecycle-functions.png)
176176

177-
### `init` function
178-
179-
This function is called before a run attempt:
180-
181-
```ts /trigger/init.ts
182-
export const taskWithInit = task({
183-
id: "task-with-init",
184-
init: async ({ payload, ctx }) => {
185-
//...
186-
},
187-
run: async (payload: any, { ctx }) => {
188-
//...
189-
},
190-
});
191-
```
192-
193-
You can also return data from the `init` function that will be available in the params of the `run`, `cleanup`, `onSuccess`, and `onFailure` functions.
194-
195-
```ts /trigger/init-return.ts
196-
export const taskWithInitReturn = task({
197-
id: "task-with-init-return",
198-
init: async ({ payload, ctx }) => {
199-
return { someData: "someValue" };
200-
},
201-
run: async (payload: any, { ctx, init }) => {
202-
console.log(init.someData); // "someValue"
203-
},
204-
});
205-
```
206-
207-
<Info>Errors thrown in the `init` function are ignored.</Info>
208-
209-
### `cleanup` function
210-
211-
This function is called after the `run` function is executed, regardless of whether the run was successful or not. It's useful for cleaning up resources, logging, or other side effects.
212-
213-
```ts /trigger/cleanup.ts
214-
export const taskWithCleanup = task({
215-
id: "task-with-cleanup",
216-
cleanup: async ({ payload, ctx }) => {
217-
//...
218-
},
219-
run: async (payload: any, { ctx }) => {
220-
//...
221-
},
222-
});
223-
```
224-
225-
<Info>Errors thrown in the `cleanup` function will fail the attempt.</Info>
226-
227177
### `middleware` and `locals` functions
228178

229179
Our task middleware system runs at the top level, executing before and after all lifecycle hooks. This allows you to wrap the entire task execution lifecycle with custom logic.
230180

231181
<Info>
232182
An error thrown in `middleware` is just like an uncaught error in the run function: it will
233-
propagate through to `catchError()` function and then will fail the attempt (causing a retry).
183+
propagate through to `catchError()` function and then will fail the attempt (either causing a
184+
retry or failing the run).
234185
</Info>
235186

236187
The `locals` API allows you to share data between middleware and hooks.
@@ -298,7 +249,12 @@ export const myTask = task({
298249

299250
### `onStart` function
300251

301-
When a task run starts, the `onStart` function is called. It's useful for sending notifications, logging, and other side effects. This function will only be called one per run (not per retry). If you want to run code before each retry, use the `init` function.
252+
When a task run starts, the `onStart` function is called. It's useful for sending notifications, logging, and other side effects.
253+
254+
<Warning>
255+
This function will only be called once per run (not per attempt). If you want to run code before
256+
each attempt, use a middleware function.
257+
</Warning>
302258

303259
```ts /trigger/on-start.ts
304260
export const taskWithOnStart = task({
@@ -312,20 +268,17 @@ export const taskWithOnStart = task({
312268
});
313269
```
314270

315-
You can also define an `onStart` function in your `trigger.config.ts` file to get notified when any task starts.
271+
You can also define a global `onStart` function using `tasks.onStart()`.
316272

317-
```ts trigger.config.ts
318-
import { defineConfig } from "@trigger.dev/sdk";
273+
```ts init.ts
274+
import { tasks } from "@trigger.dev/sdk";
319275

320-
export default defineConfig({
321-
project: "proj_1234",
322-
onStart: async ({ payload, ctx }) => {
323-
console.log("Task started", ctx.task.id);
324-
},
276+
tasks.onStart(({ ctx, payload, task }) => {
277+
console.log(`Run ${ctx.run.id} started on task ${task}`, ctx.run);
325278
});
326279
```
327280

328-
<Info>Errors thrown in the `onStart` function are ignored.</Info>
281+
<Info>Errors thrown in the `onStart` function will cause the attempt to fail.</Info>
329282

330283
### `onWait` and `onResume` functions
331284

@@ -350,6 +303,20 @@ export const myTask = task({
350303
});
351304
```
352305

306+
You can also define global `onWait` and `onResume` functions using `tasks.onWait()` and `tasks.onResume()`:
307+
308+
```ts init.ts
309+
import { tasks } from "@trigger.dev/sdk";
310+
311+
tasks.onWait(({ ctx, payload, wait, task }) => {
312+
console.log("Run paused", ctx.run, wait);
313+
});
314+
315+
tasks.onResume(({ ctx, payload, wait, task }) => {
316+
console.log("Run resumed", ctx.run, wait);
317+
});
318+
```
319+
353320
### `onSuccess` function
354321

355322
When a task run succeeds, the `onSuccess` function is called. It's useful for sending notifications, logging, syncing state to your database, or other side effects.
@@ -366,20 +333,20 @@ export const taskWithOnSuccess = task({
366333
});
367334
```
368335

369-
You can also define an `onSuccess` function in your `trigger.config.ts` file to get notified when any task succeeds.
336+
You can also define a global `onSuccess` function using `tasks.onSuccess()`.
370337

371-
```ts trigger.config.ts
372-
import { defineConfig } from "@trigger.dev/sdk";
338+
```ts init.ts
339+
import { tasks } from "@trigger.dev/sdk";
373340

374-
export default defineConfig({
375-
project: "proj_1234",
376-
onSuccess: async ({ payload, output, ctx }) => {
377-
console.log("Task succeeded", ctx.task.id);
378-
},
341+
tasks.onSuccess(({ ctx, payload, output }) => {
342+
console.log("Task succeeded", ctx.task.id);
379343
});
380344
```
381345

382-
<Info>Errors thrown in the `onSuccess` function are ignored.</Info>
346+
<Info>
347+
Errors thrown in the `onSuccess` function will be ignored, but you will still be able to see them
348+
in the dashboard.
349+
</Info>
383350

384351
### `onComplete` function
385352

@@ -397,6 +364,21 @@ export const taskWithOnComplete = task({
397364
});
398365
```
399366
367+
You can also define a global `onComplete` function using `tasks.onComplete()`.
368+
369+
```ts init.ts
370+
import { tasks } from "@trigger.dev/sdk";
371+
372+
tasks.onComplete(({ ctx, payload, output }) => {
373+
console.log("Task completed", ctx.task.id);
374+
});
375+
```
376+
377+
<Info>
378+
Errors thrown in the `onComplete` function will be ignored, but you will still be able to see them
379+
in the dashboard.
380+
</Info>
381+
400382
### `onFailure` function
401383
402384
When a task run fails, the `onFailure` function is called. It's useful for sending notifications, logging, or other side effects. It will only be executed once the task run has exhausted all its retries.
@@ -413,20 +395,20 @@ export const taskWithOnFailure = task({
413395
});
414396
```
415397
416-
You can also define an `onFailure` function in your `trigger.config.ts` file to get notified when any task fails.
398+
You can also define a global `onFailure` function using `tasks.onFailure()`.
417399
418-
```ts trigger.config.ts
419-
import { defineConfig } from "@trigger.dev/sdk";
400+
```ts init.ts
401+
import { tasks } from "@trigger.dev/sdk";
420402

421-
export default defineConfig({
422-
project: "proj_1234",
423-
onFailure: async ({ payload, error, ctx }) => {
424-
console.log("Task failed", ctx.task.id);
425-
},
403+
tasks.onFailure(({ ctx, payload, error }) => {
404+
console.log("Task failed", ctx.task.id);
426405
});
427406
```
428407
429-
<Info>Errors thrown in the `onFailure` function are ignored.</Info>
408+
<Info>
409+
Errors thrown in the `onFailure` function will be ignored, but you will still be able to see them
410+
in the dashboard.
411+
</Info>
430412
431413
<Note>
432414
`onFailure` doesn’t fire for some of the run statuses like `Crashed`, `System failures`, and
@@ -441,7 +423,7 @@ Read more about `catchError` in our [Errors and Retrying guide](/errors-retrying
441423
442424
<Info>Uncaught errors will throw a special internal error of the type `HANDLE_ERROR_ERROR`.</Info>
443425
444-
### onCancel
426+
### `onCancel` function
445427
446428
You can define an `onCancel` hook that is called when a run is cancelled. This is useful if you want to clean up any resources that were allocated for the run.
447429
@@ -540,6 +522,66 @@ export const cancelExampleTask = task({
540522
point the process will be killed.
541523
</Note>
542524
525+
### `init` function (deprecated)
526+
527+
<Warning>
528+
The `init` hook is deprecated and will be removed in the future. Use
529+
[middleware](/tasks/overview#middleware-and-locals-functions) instead.
530+
</Warning>
531+
532+
This function is called before a run attempt:
533+
534+
```ts /trigger/init.ts
535+
export const taskWithInit = task({
536+
id: "task-with-init",
537+
init: async ({ payload, ctx }) => {
538+
//...
539+
},
540+
run: async (payload: any, { ctx }) => {
541+
//...
542+
},
543+
});
544+
```
545+
546+
You can also return data from the `init` function that will be available in the params of the `run`, `cleanup`, `onSuccess`, and `onFailure` functions.
547+
548+
```ts /trigger/init-return.ts
549+
export const taskWithInitReturn = task({
550+
id: "task-with-init-return",
551+
init: async ({ payload, ctx }) => {
552+
return { someData: "someValue" };
553+
},
554+
run: async (payload: any, { ctx, init }) => {
555+
console.log(init.someData); // "someValue"
556+
},
557+
});
558+
```
559+
560+
<Info>Errors thrown in the `init` function will cause the attempt to fail.</Info>
561+
562+
### `cleanup` function (deprecated)
563+
564+
<Warning>
565+
The `cleanup` hook is deprecated and will be removed in the future. Use
566+
[middleware](/tasks/overview#middleware-and-locals-functions) instead.
567+
</Warning>
568+
569+
This function is called after the `run` function is executed, regardless of whether the run was successful or not. It's useful for cleaning up resources, logging, or other side effects.
570+
571+
```ts /trigger/cleanup.ts
572+
export const taskWithCleanup = task({
573+
id: "task-with-cleanup",
574+
cleanup: async ({ payload, ctx }) => {
575+
//...
576+
},
577+
run: async (payload: any, { ctx }) => {
578+
//...
579+
},
580+
});
581+
```
582+
583+
<Info>Errors thrown in the `cleanup` function will cause the attempt to fail.</Info>
584+
543585
## Next steps
544586
545587
<CardGroup>

packages/core/src/v3/workers/taskExecutor.ts

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,7 @@ export class TaskExecutor {
795795

796796
return await runTimelineMetrics.measureMetric("trigger.dev/execution", "success", async () => {
797797
for (const hook of globalSuccessHooks) {
798-
const [hookError] = await tryCatch(
798+
await tryCatch(
799799
this._tracer.startActiveSpan(
800800
"onSuccess()",
801801
async (span) => {
@@ -817,14 +817,10 @@ export class TaskExecutor {
817817
}
818818
)
819819
);
820-
821-
if (hookError) {
822-
throw hookError;
823-
}
824820
}
825821

826822
if (taskSuccessHook) {
827-
const [hookError] = await tryCatch(
823+
await tryCatch(
828824
this._tracer.startActiveSpan(
829825
"onSuccess()",
830826
async (span) => {
@@ -846,10 +842,6 @@ export class TaskExecutor {
846842
}
847843
)
848844
);
849-
850-
if (hookError) {
851-
throw hookError;
852-
}
853845
}
854846
});
855847
}
@@ -870,7 +862,7 @@ export class TaskExecutor {
870862

871863
return await runTimelineMetrics.measureMetric("trigger.dev/execution", "failure", async () => {
872864
for (const hook of globalFailureHooks) {
873-
const [hookError] = await tryCatch(
865+
await tryCatch(
874866
this._tracer.startActiveSpan(
875867
"onFailure()",
876868
async (span) => {
@@ -892,14 +884,10 @@ export class TaskExecutor {
892884
}
893885
)
894886
);
895-
896-
if (hookError) {
897-
throw hookError;
898-
}
899887
}
900888

901889
if (taskFailureHook) {
902-
const [hookError] = await tryCatch(
890+
await tryCatch(
903891
this._tracer.startActiveSpan(
904892
"onFailure()",
905893
async (span) => {
@@ -921,10 +909,6 @@ export class TaskExecutor {
921909
}
922910
)
923911
);
924-
925-
if (hookError) {
926-
throw hookError;
927-
}
928912
}
929913
});
930914
}
@@ -1315,7 +1299,7 @@ export class TaskExecutor {
13151299

13161300
return await runTimelineMetrics.measureMetric("trigger.dev/execution", "complete", async () => {
13171301
for (const hook of globalCompleteHooks) {
1318-
const [hookError] = await tryCatch(
1302+
await tryCatch(
13191303
this._tracer.startActiveSpan(
13201304
"onComplete()",
13211305
async (span) => {
@@ -1337,14 +1321,10 @@ export class TaskExecutor {
13371321
}
13381322
)
13391323
);
1340-
1341-
if (hookError) {
1342-
throw hookError;
1343-
}
13441324
}
13451325

13461326
if (taskCompleteHook) {
1347-
const [hookError] = await tryCatch(
1327+
await tryCatch(
13481328
this._tracer.startActiveSpan(
13491329
"onComplete()",
13501330
async (span) => {
@@ -1366,10 +1346,6 @@ export class TaskExecutor {
13661346
}
13671347
)
13681348
);
1369-
1370-
if (hookError) {
1371-
throw hookError;
1372-
}
13731349
}
13741350
});
13751351
}

0 commit comments

Comments
 (0)