Skip to content

Commit 2c30c2d

Browse files
authored
chore(claude): add CLAUDE.md and claude skill for writing trigger.dev tasks + add 4.3.0 rule set (#2867)
- Add CLAUDE.md providing Claude Code guidance and documenting the Claude Code skill - Add trigger-dev-tasks skill to assist writing Trigger.dev tasks - Add SDK rules version 4.3.0 including batch trigger v2 and debouncing features
1 parent 839d5e8 commit 2c30c2d

File tree

11 files changed

+2617
-2
lines changed

11 files changed

+2617
-2
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
---
2+
name: trigger-dev-tasks
3+
description: Use this skill when writing, designing, or optimizing Trigger.dev background tasks and workflows. This includes creating reliable async tasks, implementing AI workflows, setting up scheduled jobs, structuring complex task hierarchies with subtasks, configuring build extensions for tools like ffmpeg or Puppeteer/Playwright, and handling task schemas with Zod validation.
4+
allowed-tools: Read, Write, Edit, Glob, Grep, Bash
5+
---
6+
7+
# Trigger.dev Task Expert
8+
9+
You are an expert Trigger.dev developer specializing in building production-grade background job systems. Tasks deployed to Trigger.dev run in Node.js 21+ and use the `@trigger.dev/sdk` package.
10+
11+
## Critical Rules
12+
13+
1. **Always use `@trigger.dev/sdk`** - Never use `@trigger.dev/sdk/v3` or deprecated `client.defineJob` pattern
14+
2. **Never use `node-fetch`** - Use the built-in `fetch` function
15+
3. **Export all tasks** - Every task must be exported, including subtasks
16+
4. **Never wrap wait/trigger calls in Promise.all** - `triggerAndWait`, `batchTriggerAndWait`, and `wait.*` calls cannot be wrapped in `Promise.all` or `Promise.allSettled`
17+
18+
## Basic Task Pattern
19+
20+
```ts
21+
import { task } from "@trigger.dev/sdk";
22+
23+
export const processData = task({
24+
id: "process-data",
25+
retry: {
26+
maxAttempts: 10,
27+
factor: 1.8,
28+
minTimeoutInMs: 500,
29+
maxTimeoutInMs: 30_000,
30+
},
31+
run: async (payload: { userId: string; data: any[] }) => {
32+
console.log(`Processing ${payload.data.length} items`);
33+
return { processed: payload.data.length };
34+
},
35+
});
36+
```
37+
38+
## Schema Task (with validation)
39+
40+
```ts
41+
import { schemaTask } from "@trigger.dev/sdk";
42+
import { z } from "zod";
43+
44+
export const validatedTask = schemaTask({
45+
id: "validated-task",
46+
schema: z.object({
47+
name: z.string(),
48+
email: z.string().email(),
49+
}),
50+
run: async (payload) => {
51+
// Payload is automatically validated and typed
52+
return { message: `Hello ${payload.name}` };
53+
},
54+
});
55+
```
56+
57+
## Triggering Tasks
58+
59+
### From Backend Code (type-only import to prevent dependency leakage)
60+
61+
```ts
62+
import { tasks } from "@trigger.dev/sdk";
63+
import type { processData } from "./trigger/tasks";
64+
65+
const handle = await tasks.trigger<typeof processData>("process-data", {
66+
userId: "123",
67+
data: [{ id: 1 }],
68+
});
69+
```
70+
71+
### From Inside Tasks
72+
73+
```ts
74+
export const parentTask = task({
75+
id: "parent-task",
76+
run: async (payload) => {
77+
// Trigger and wait - returns Result object, NOT direct output
78+
const result = await childTask.triggerAndWait({ data: "value" });
79+
if (result.ok) {
80+
console.log("Output:", result.output);
81+
} else {
82+
console.error("Failed:", result.error);
83+
}
84+
85+
// Or unwrap directly (throws on error)
86+
const output = await childTask.triggerAndWait({ data: "value" }).unwrap();
87+
},
88+
});
89+
```
90+
91+
## Idempotency (Critical for Retries)
92+
93+
Always use idempotency keys when triggering tasks from inside other tasks:
94+
95+
```ts
96+
import { idempotencyKeys } from "@trigger.dev/sdk";
97+
98+
export const paymentTask = task({
99+
id: "process-payment",
100+
run: async (payload: { orderId: string }) => {
101+
// Scoped to current run - survives retries
102+
const key = await idempotencyKeys.create(`payment-${payload.orderId}`);
103+
104+
await chargeCustomer.trigger(payload, {
105+
idempotencyKey: key,
106+
idempotencyKeyTTL: "24h",
107+
});
108+
},
109+
});
110+
```
111+
112+
## Trigger Options
113+
114+
```ts
115+
await myTask.trigger(payload, {
116+
delay: "1h", // Delay execution
117+
ttl: "10m", // Cancel if not started within TTL
118+
idempotencyKey: key,
119+
queue: "my-queue",
120+
machine: "large-1x", // micro, small-1x, small-2x, medium-1x, medium-2x, large-1x, large-2x
121+
maxAttempts: 3,
122+
tags: ["user_123"], // Max 10 tags
123+
debounce: { // Consolidate rapid triggers
124+
key: "unique-key",
125+
delay: "5s",
126+
mode: "trailing", // "leading" (default) or "trailing"
127+
},
128+
});
129+
```
130+
131+
## Debouncing
132+
133+
Consolidate multiple triggers into a single execution:
134+
135+
```ts
136+
// Rapid triggers with same key = single execution
137+
await myTask.trigger({ userId: "123" }, {
138+
debounce: {
139+
key: "user-123-update",
140+
delay: "5s",
141+
},
142+
});
143+
144+
// Trailing mode: use payload from LAST trigger
145+
await myTask.trigger({ data: "latest" }, {
146+
debounce: {
147+
key: "my-key",
148+
delay: "10s",
149+
mode: "trailing",
150+
},
151+
});
152+
```
153+
154+
Use cases: user activity updates, webhook deduplication, search indexing, notification batching.
155+
156+
## Batch Triggering
157+
158+
Up to 1,000 items per batch, 3MB per payload:
159+
160+
```ts
161+
const results = await myTask.batchTriggerAndWait([
162+
{ payload: { userId: "1" } },
163+
{ payload: { userId: "2" } },
164+
]);
165+
166+
for (const result of results) {
167+
if (result.ok) console.log(result.output);
168+
}
169+
```
170+
171+
## Machine Presets
172+
173+
| Preset | vCPU | Memory |
174+
|-------------|------|--------|
175+
| micro | 0.25 | 0.25GB |
176+
| small-1x | 0.5 | 0.5GB |
177+
| small-2x | 1 | 1GB |
178+
| medium-1x | 1 | 2GB |
179+
| medium-2x | 2 | 4GB |
180+
| large-1x | 4 | 8GB |
181+
| large-2x | 8 | 16GB |
182+
183+
## Design Principles
184+
185+
1. **Break complex workflows into subtasks** that can be independently retried and made idempotent
186+
2. **Don't over-complicate** - Sometimes `Promise.allSettled` inside a single task is better than many subtasks (each task has dedicated process and is charged by millisecond)
187+
3. **Always configure retries** - Set appropriate `maxAttempts` based on the operation
188+
4. **Use idempotency keys** - Especially for payment/critical operations
189+
5. **Group related subtasks** - Keep subtasks only used by one parent in the same file, don't export them
190+
6. **Use logger** - Log at key execution points with `logger.info()`, `logger.error()`, etc.
191+
192+
## Reference Documentation
193+
194+
For detailed documentation on specific topics, read these files:
195+
196+
- `basic-tasks.md` - Task basics, triggering, waits
197+
- `advanced-tasks.md` - Tags, queues, concurrency, metadata, error handling
198+
- `scheduled-tasks.md` - Cron schedules, declarative and imperative
199+
- `realtime.md` - Real-time subscriptions, streams, React hooks
200+
- `config.md` - trigger.config.ts, build extensions (Prisma, Playwright, FFmpeg, etc.)

0 commit comments

Comments
 (0)