Skip to content

Commit 1edd66e

Browse files
authored
Merge branch 'main' into feat/sync-env-secrets
2 parents 4a13ef6 + 5693b62 commit 1edd66e

1,656 files changed

Lines changed: 282130 additions & 32002 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/angry-trainers-perform.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

.changeset/kind-kids-teach.md

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/core": patch
3+
---
4+
5+
Truncate large error stacks and messages to prevent OOM crashes. Stack traces are capped at 50 frames (keeping top 5 + bottom 45 with an omission notice), individual stack lines at 1024 chars, and error messages at 1000 chars. Applied in parseError, sanitizeError, and OTel span recording.

.claude/rules/database-safety.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
paths:
3+
- "internal-packages/database/**"
4+
---
5+
6+
# Database Migration Safety
7+
8+
- When adding indexes to **existing tables**, use `CREATE INDEX CONCURRENTLY IF NOT EXISTS` to avoid table locks. These must be in their own separate migration file (one index per file).
9+
- Indexes on **newly created tables** (same migration as `CREATE TABLE`) do not need CONCURRENTLY.
10+
- When indexing a **new column on an existing table**, split into two migrations: first `ADD COLUMN IF NOT EXISTS`, then `CREATE INDEX CONCURRENTLY IF NOT EXISTS` in a separate file.
11+
- After generating a migration with Prisma, remove extraneous lines for: `_BackgroundWorkerToBackgroundWorkerFile`, `_BackgroundWorkerToTaskQueue`, `_TaskRunToTaskRunTag`, `_WaitpointRunConnections`, `_completedWaitpoints`, `SecretStore_key_idx`, and unrelated TaskRun indexes.
12+
- Never drop columns or tables without explicit approval.
13+
- New code should target `RunEngineVersion.V2` only.

.claude/rules/docs-writing.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
paths:
3+
- "docs/**"
4+
---
5+
6+
# Documentation Writing Rules
7+
8+
- Use Mintlify MDX format. Frontmatter: `title`, `description`, `sidebarTitle` (optional).
9+
- After creating a new page, add it to `docs.json` navigation under the correct group.
10+
- Use Mintlify components: `<Note>`, `<Warning>`, `<Info>`, `<Tip>`, `<CodeGroup>`, `<Expandable>`, `<Steps>`/`<Step>`.
11+
- Code examples should be complete and runnable where possible.
12+
- Always import from `@trigger.dev/sdk`, never `@trigger.dev/sdk/v3`.
13+
- Keep paragraphs short. Use headers to break up content.
14+
- Link to related pages using relative paths (e.g., `[Tasks](/tasks/overview)`).

.claude/rules/legacy-v3-code.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
paths:
3+
- "apps/webapp/app/v3/**"
4+
---
5+
6+
# Legacy V1 Engine Code in `app/v3/`
7+
8+
The `v3/` directory name is misleading - most code here is actively used by the current V2 engine. Only the specific files below are legacy V1-only code.
9+
10+
## V1-Only Files - Never Modify
11+
12+
- `marqs/` directory (entire MarQS queue system: sharedQueueConsumer, devQueueConsumer, fairDequeuingStrategy, devPubSub)
13+
- `legacyRunEngineWorker.server.ts` (V1 background job worker)
14+
- `services/triggerTaskV1.server.ts` (deprecated V1 task triggering)
15+
- `services/cancelTaskRunV1.server.ts` (deprecated V1 cancellation)
16+
- `authenticatedSocketConnection.server.ts` (V1 dev WebSocket using DevQueueConsumer)
17+
- `sharedSocketConnection.ts` (V1 shared queue socket using SharedQueueConsumer)
18+
19+
## V1/V2 Branching Pattern
20+
21+
Some services act as routers that branch on `RunEngineVersion`:
22+
- `services/cancelTaskRun.server.ts` - calls V1 service or `engine.cancelRun()` for V2
23+
- `services/batchTriggerV3.server.ts` - uses marqs for V1 path, run-engine for V2
24+
25+
When editing these shared services, only modify V2 code paths.
26+
27+
## V2 Modern Stack
28+
29+
- **Run lifecycle**: `@internal/run-engine` (internal-packages/run-engine)
30+
- **Background jobs**: `@trigger.dev/redis-worker` (not graphile-worker/zodworker)
31+
- **Queue operations**: RunQueue inside run-engine (not MarQS)
32+
- **V2 engine singleton**: `runEngine.server.ts`, `runEngineHandlers.server.ts`
33+
- **V2 workers**: `commonWorker.server.ts`, `alertsWorker.server.ts`, `batchTriggerWorker.server.ts`

.claude/rules/sdk-packages.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
paths:
3+
- "packages/**"
4+
---
5+
6+
# Public Package Rules
7+
8+
- Changes to `packages/` are **customer-facing**. Always add a changeset: `pnpm run changeset:add`
9+
- Default to **patch**. Get maintainer approval for minor. Never select major without explicit approval.
10+
- `@trigger.dev/core`: **Never import the root**. Always use subpath imports (e.g., `@trigger.dev/core/v3`).
11+
- Do NOT update `rules/` or `.claude/skills/trigger-dev-tasks/` unless explicitly asked. These are maintained in separate dedicated passes.
12+
- Test changes using `references/hello-world` reference project.

.claude/rules/server-apps.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
paths:
3+
- "apps/**"
4+
---
5+
6+
# Server App Changes
7+
8+
When modifying server apps (webapp, supervisor, coordinator, etc.) with **no package changes**, add a `.server-changes/` file instead of a changeset:
9+
10+
```bash
11+
cat > .server-changes/descriptive-name.md << 'EOF'
12+
---
13+
area: webapp
14+
type: fix
15+
---
16+
17+
Brief description of what changed and why.
18+
EOF
19+
```
20+
21+
- **area**: `webapp` | `supervisor` | `coordinator` | `kubernetes-provider` | `docker-provider`
22+
- **type**: `feature` | `fix` | `improvement` | `breaking`
23+
- If the PR also touches `packages/`, just the changeset is sufficient (no `.server-changes/` needed).
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
name: span-timeline-events
3+
description: Use when adding, modifying, or debugging OTel span timeline events in the trace view. Covers event structure, ClickHouse storage constraints, rendering in SpanTimeline component, admin visibility, and the step-by-step process for adding new events.
4+
allowed-tools: Read, Write, Edit, Glob, Grep, Bash
5+
---
6+
7+
# Span Timeline Events
8+
9+
The trace view's right panel shows a timeline of events for the selected span. These are OTel span events rendered by `app/utils/timelineSpanEvents.ts` and the `SpanTimeline` component.
10+
11+
## How They Work
12+
13+
1. **Span events** in OTel are attached to a parent span. In ClickHouse, they're stored as separate rows with `kind: "SPAN_EVENT"` sharing the parent span's `span_id`. The `#mergeRecordsIntoSpanDetail` method reassembles them into the span's `events` array at query time.
14+
2. The timeline only renders events whose `name` starts with `trigger.dev/` - all others are silently filtered out.
15+
3. The **display name** comes from `properties.event` (not the span event name), mapped through `getFriendlyNameForEvent()`.
16+
4. Events are shown on the **span they belong to** - events on one span don't appear in another span's timeline.
17+
18+
## ClickHouse Storage Constraint
19+
20+
When events are written to ClickHouse, `spanEventsToTaskEventV1Input()` filters out events whose `start_time` is not greater than the parent span's `startTime`. Events at or before the span start are silently dropped. This means span events must have timestamps strictly after the span's own `startTimeUnixNano`.
21+
22+
## Timeline Rendering (SpanTimeline component)
23+
24+
The `SpanTimeline` component in `app/components/run/RunTimeline.tsx` renders:
25+
26+
1. **Events** (thin 1px line with hollow dots) - all events from `createTimelineSpanEventsFromSpanEvents()`
27+
2. **"Started"** marker (thick cap) - at the span's `startTime`
28+
3. **Duration bar** (thick 7px line) - from "Started" to "Finished"
29+
4. **"Finished"** marker (thick cap) - at `startTime + duration`
30+
31+
The thin line before "Started" only appears when there are events with timestamps between the span start and the first child span. For the Attempt span this works well (Dequeued -> Pod scheduled -> Launched -> etc. all happen before execution starts). Events all get `lineVariant: "light"` (thin) while the execution bar gets `variant: "normal"` (thick).
32+
33+
## Trace View Sort Order
34+
35+
Sibling spans (same parent) are sorted by `start_time ASC` from the ClickHouse query. The `createTreeFromFlatItems` function preserves this order. Event timestamps don't affect sort order - only the span's own `start_time`.
36+
37+
## Event Structure
38+
39+
```typescript
40+
// OTel span event format
41+
{
42+
name: "trigger.dev/run", // Must start with "trigger.dev/" to render
43+
timeUnixNano: "1711200000000000000",
44+
attributes: [
45+
{ key: "event", value: { stringValue: "dequeue" } }, // The actual event type
46+
{ key: "duration", value: { intValue: 150 } }, // Optional: duration in ms
47+
]
48+
}
49+
```
50+
51+
## Admin-Only Events
52+
53+
`getAdminOnlyForEvent()` controls visibility. Events default to **admin-only** (`true`).
54+
55+
| Event | Admin-only | Friendly name |
56+
|-------|-----------|---------------|
57+
| `dequeue` | No | Dequeued |
58+
| `fork` | No | Launched |
59+
| `import` | No (if no fork event) | Importing task file |
60+
| `create_attempt` | Yes | Attempt created |
61+
| `lazy_payload` | Yes | Lazy attempt initialized |
62+
| `pod_scheduled` | Yes | Pod scheduled |
63+
| (default) | Yes | (raw event name) |
64+
65+
## Adding New Timeline Events
66+
67+
1. Add OTLP span event with `name: "trigger.dev/<scope>"` and `properties.event: "<type>"`
68+
2. Event timestamp must be strictly after the parent span's `startTimeUnixNano` (ClickHouse drops earlier events)
69+
3. Add friendly name in `getFriendlyNameForEvent()` in `app/utils/timelineSpanEvents.ts`
70+
4. Set admin visibility in `getAdminOnlyForEvent()`
71+
5. Optionally add help text in `getHelpTextForEvent()`
72+
73+
## Key Files
74+
75+
- `app/utils/timelineSpanEvents.ts` - filtering, naming, admin logic
76+
- `app/components/run/RunTimeline.tsx` - `SpanTimeline` component (thin line + thick bar rendering)
77+
- `app/presenters/v3/SpanPresenter.server.ts` - loads span data including events
78+
- `app/v3/eventRepository/clickhouseEventRepository.server.ts` - `spanEventsToTaskEventV1Input()` (storage filter), `#mergeRecordsIntoSpanDetail` (reassembly)
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)