@trigger.dev/sdk 4.7.3 → 4.7.5

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 (119) hide show
  1. package/dist/commonjs/v3/ai.d.ts +26 -0
  2. package/dist/commonjs/v3/ai.js +7 -5
  3. package/dist/commonjs/v3/ai.js.map +1 -1
  4. package/dist/commonjs/v3/concurrencyLimits.d.ts +37 -7
  5. package/dist/commonjs/v3/concurrencyLimits.js +38 -70
  6. package/dist/commonjs/v3/concurrencyLimits.js.map +1 -1
  7. package/dist/commonjs/v3/index.d.ts +1 -1
  8. package/dist/commonjs/v3/index.js.map +1 -1
  9. package/dist/commonjs/v3/query.d.ts +434 -1
  10. package/dist/commonjs/v3/query.js +115 -0
  11. package/dist/commonjs/v3/query.js.map +1 -1
  12. package/dist/commonjs/v3/runs.d.ts +22 -1
  13. package/dist/commonjs/v3/runs.js +6 -3
  14. package/dist/commonjs/v3/runs.js.map +1 -1
  15. package/dist/commonjs/version.js +1 -1
  16. package/dist/esm/v3/ai.d.ts +26 -0
  17. package/dist/esm/v3/ai.js +7 -5
  18. package/dist/esm/v3/ai.js.map +1 -1
  19. package/dist/esm/v3/concurrencyLimits.d.ts +37 -7
  20. package/dist/esm/v3/concurrencyLimits.js +38 -70
  21. package/dist/esm/v3/concurrencyLimits.js.map +1 -1
  22. package/dist/esm/v3/index.d.ts +1 -1
  23. package/dist/esm/v3/index.js.map +1 -1
  24. package/dist/esm/v3/query.d.ts +434 -1
  25. package/dist/esm/v3/query.js +115 -0
  26. package/dist/esm/v3/query.js.map +1 -1
  27. package/dist/esm/v3/runs.d.ts +22 -1
  28. package/dist/esm/v3/runs.js +6 -3
  29. package/dist/esm/v3/runs.js.map +1 -1
  30. package/dist/esm/version.js +1 -1
  31. package/docs/ai-chat/actions.mdx +6 -1
  32. package/docs/ai-chat/backend.mdx +47 -0
  33. package/docs/ai-chat/patterns/redacting-telemetry.mdx +117 -0
  34. package/docs/ai-chat/patterns/sub-agents.mdx +33 -0
  35. package/docs/ai-chat/pending-messages.mdx +2 -0
  36. package/docs/ai-chat/reference.mdx +53 -2
  37. package/docs/ai-chat/transcript-storage.mdx +1 -1
  38. package/docs/apikeys.mdx +9 -7
  39. package/docs/billing-limits.mdx +1 -1
  40. package/docs/cli-env-commands.mdx +5 -4
  41. package/docs/cli-init-commands.mdx +27 -1
  42. package/docs/cli-login-commands.mdx +20 -0
  43. package/docs/cli-projects-commands.mdx +13 -1
  44. package/docs/cli-runs-commands.mdx +3 -2
  45. package/docs/cli-tasks-commands.mdx +95 -0
  46. package/docs/cli-update-commands.mdx +9 -3
  47. package/docs/cli-whoami-commands.mdx +12 -0
  48. package/docs/concurrency.mdx +32 -0
  49. package/docs/config/config-file.mdx +58 -0
  50. package/docs/config/extensions/audioWaveform.mdx +4 -1
  51. package/docs/config/extensions/overview.mdx +2 -0
  52. package/docs/config/extensions/prismaExtension.mdx +6 -7
  53. package/docs/config/extensions/puppeteer.mdx +3 -5
  54. package/docs/config/extensions/pythonExtension.mdx +9 -1
  55. package/docs/config/extensions/syncEnvVars.mdx +6 -0
  56. package/docs/deploy-environment-variables.mdx +5 -1
  57. package/docs/deployment/atomic-deployment.mdx +5 -3
  58. package/docs/deployment/dev-branches.mdx +5 -5
  59. package/docs/deployment/overview.mdx +9 -8
  60. package/docs/deployment/preview-branches.mdx +7 -4
  61. package/docs/errors-retrying.mdx +4 -4
  62. package/docs/github-actions.mdx +3 -3
  63. package/docs/how-it-works.mdx +5 -5
  64. package/docs/introduction.mdx +6 -0
  65. package/docs/limits.mdx +32 -8
  66. package/docs/logging.mdx +1 -1
  67. package/docs/machines.mdx +1 -1
  68. package/docs/manual-setup.mdx +1 -1
  69. package/docs/mcp-introduction.mdx +8 -7
  70. package/docs/mcp-tools.mdx +113 -3
  71. package/docs/migrating-from-v3.mdx +1 -1
  72. package/docs/observability/dashboards.mdx +64 -0
  73. package/docs/observability/query.mdx +6 -2
  74. package/docs/private-networking/aws-console-setup.mdx +1 -1
  75. package/docs/private-networking/overview.mdx +1 -1
  76. package/docs/quick-start.mdx +3 -40
  77. package/docs/realtime/auth.mdx +1 -1
  78. package/docs/realtime/backend/input-streams.mdx +159 -0
  79. package/docs/realtime/backend/streams.mdx +25 -10
  80. package/docs/realtime/how-it-works.mdx +1 -1
  81. package/docs/realtime/react-hooks/overview.mdx +1 -1
  82. package/docs/realtime/react-hooks/streams.mdx +2 -2
  83. package/docs/realtime/react-hooks/subscribe.mdx +19 -3
  84. package/docs/realtime/react-hooks/triggering.mdx +2 -2
  85. package/docs/realtime/react-hooks/use-wait-token.mdx +1 -1
  86. package/docs/realtime/run-object.mdx +47 -24
  87. package/docs/replaying.mdx +26 -1
  88. package/docs/reports.mdx +4 -2
  89. package/docs/run-tests.mdx +2 -2
  90. package/docs/run-usage.mdx +3 -3
  91. package/docs/runs/heartbeats.mdx +2 -2
  92. package/docs/runs/list-best-practices.mdx +234 -0
  93. package/docs/runs/max-duration.mdx +4 -0
  94. package/docs/runs/metadata.mdx +7 -10
  95. package/docs/runs.mdx +18 -8
  96. package/docs/self-hosting/env/supervisor.mdx +3 -0
  97. package/docs/self-hosting/env/webapp.mdx +5 -0
  98. package/docs/self-hosting/overview.mdx +26 -0
  99. package/docs/tags.mdx +6 -0
  100. package/docs/tasks/scheduled.mdx +11 -5
  101. package/docs/triggering.mdx +8 -0
  102. package/docs/troubleshooting-alerts.mdx +102 -3
  103. package/docs/troubleshooting.mdx +1 -1
  104. package/docs/upgrading-packages.mdx +3 -3
  105. package/docs/vercel-integration.mdx +13 -3
  106. package/docs/versioning.mdx +1 -1
  107. package/docs/wait-for-token.mdx +3 -3
  108. package/docs/webhooks/channels.mdx +158 -0
  109. package/docs/webhooks/connect.mdx +84 -0
  110. package/docs/webhooks/deliveries.mdx +103 -0
  111. package/docs/webhooks/filters.mdx +144 -0
  112. package/docs/webhooks/human-in-the-loop.mdx +162 -0
  113. package/docs/webhooks/linear.mdx +190 -0
  114. package/docs/webhooks/overview.mdx +119 -0
  115. package/docs/webhooks/session-routing.mdx +145 -0
  116. package/docs/webhooks/slack.mdx +397 -0
  117. package/docs/webhooks/sources.mdx +221 -0
  118. package/docs/webhooks/waiters.mdx +194 -0
  119. package/package.json +3 -2
@@ -0,0 +1,234 @@
1
+ ---
2
+ title: "Using runs.list() efficiently"
3
+ sidebarTitle: "runs.list() best practices"
4
+ description: "When to use runs.list(), the patterns to avoid, and what to use instead."
5
+ ---
6
+
7
+ `runs.list()` (and `GET /api/v1/runs`) is built for browsing, reporting, and bulk operations. It is not a coordination tool. Each call searches your environment's runs in the requested time window, so its cost depends on how many runs you created in that window much more than on the page size you ask for.
8
+
9
+ A call with `limit: 4` in a busy environment can still search millions of runs to find those 4. That's fine once in a while. On a hot path, like before every trigger or in a loop every second, it slows your own code down and counts against your [API rate limit](/limits).
10
+
11
+ ## Quick reference
12
+
13
+ | You want to… | Avoid | Use instead |
14
+ | --- | --- | --- |
15
+ | Avoid piling up runs for the same work | `runs.list()` then `trigger()` | [`debounce`](/triggering#debounce) to merge a burst into one run, a [concurrency limit](/concurrency) so runs queue instead of overlapping, or an [`idempotencyKey`](/idempotency) for one run per item |
16
+ | Collapse a burst of triggers into one run | A fixed `delay` on every trigger | [`debounce`](/triggering#debounce) |
17
+ | Avoid duplicate runs for the same work | A random `idempotencyKey` per trigger | A key derived from the work itself |
18
+ | Wait for a run you triggered | Polling `runs.list()` or `runs.retrieve()` | [`triggerAndWait()`](/triggering#yourtask-triggerandwait) inside a task, or [`runs.subscribeToRun()`](/realtime/backend/subscribe) |
19
+ | Follow a group of runs | Polling `runs.list({ tag })` | [`runs.subscribeToRunsWithTag()`](/realtime/backend/subscribe) |
20
+ | Know how backed up a queue is | Counting `QUEUED` runs with `runs.list()` | [`queues.retrieve()`](/queues#retrieving-a-queue) |
21
+
22
+ ## Anti-patterns
23
+
24
+ ### Checking for active runs before triggering
25
+
26
+ This pattern tries to make sure only one run handles a piece of work at a time:
27
+
28
+ ```ts Avoid
29
+ import { runs, tasks } from "@trigger.dev/sdk";
30
+
31
+ // Called every time something changes for this account
32
+ export async function onAccountChanged(accountId: string) {
33
+ const active = await runs.list({
34
+ status: ["QUEUED", "EXECUTING"],
35
+ taskIdentifier: ["sync-account"],
36
+ tag: [`account:${accountId}`],
37
+ limit: 1,
38
+ });
39
+
40
+ if (active.data.length > 0) return;
41
+
42
+ await tasks.trigger(
43
+ "sync-account",
44
+ { accountId },
45
+ { tags: [`account:${accountId}`] }
46
+ );
47
+ }
48
+ ```
49
+
50
+ It has three problems:
51
+
52
+ - **It races.** Two calls can both see "nothing running" and both trigger.
53
+ - **It's expensive.** Every change runs a search, and the search gets slower as you create more runs.
54
+ - **It doesn't collapse bursts.** 100 changes in a second still mean 100 searches.
55
+
56
+ Let Trigger.dev do the coordination instead.
57
+
58
+ **To collapse bursts into one run, use `debounce`.** While a debounced run is still waiting for its delay, triggers with the same key push it back instead of creating new runs:
59
+
60
+ ```ts Use debounce
61
+ import { tasks } from "@trigger.dev/sdk";
62
+
63
+ export async function onAccountChanged(accountId: string) {
64
+ await tasks.trigger(
65
+ "sync-account",
66
+ { accountId },
67
+ {
68
+ debounce: {
69
+ key: `account-${accountId}`,
70
+ delay: "10s",
71
+ // The run uses the payload of the last trigger
72
+ mode: "trailing",
73
+ // Never push it back past 1m after the first trigger
74
+ maxDelay: "1m",
75
+ },
76
+ }
77
+ );
78
+ }
79
+ ```
80
+
81
+ <Tip>
82
+ With debounce, one run handles many changes. Have the task read the pending work from your own
83
+ database when it starts, instead of relying on a single trigger's payload.
84
+ </Tip>
85
+
86
+ **To never run two at once for the same key, use a per-key concurrency limit.** Extra runs wait in the queue instead of running in parallel:
87
+
88
+ ```ts Use a concurrency limit
89
+ import { task } from "@trigger.dev/sdk";
90
+
91
+ export const syncAccount = task({
92
+ id: "sync-account",
93
+ concurrency: { perKey: 1 },
94
+ run: async ({ accountId }: { accountId: string }) => {
95
+ // ...
96
+ },
97
+ });
98
+
99
+ // When triggering
100
+ await syncAccount.trigger({ accountId }, { concurrencyKey: accountId });
101
+ ```
102
+
103
+ The `concurrency` option requires SDK 4.7.0 or later. See [Concurrency](/concurrency) for details.
104
+
105
+ Debounce only merges triggers into a run that hasn't started yet. Once that run is queued or executing, the next trigger creates a new run. Combine both options so bursts collapse and a new run never overlaps one that's still going.
106
+
107
+ **For exactly one run per piece of work, use an `idempotencyKey`.** See [Using a random idempotency key](#using-a-random-idempotency-key) below.
108
+
109
+ ### Using a fixed delay to space out triggers
110
+
111
+ Adding the same `delay` to every trigger doesn't collapse anything. Each trigger still creates its own run. It just starts later:
112
+
113
+ ```ts Avoid
114
+ // 100 changes still create 100 runs, each one 30s after its trigger
115
+ await tasks.trigger("sync-account", { accountId }, { delay: "30s" });
116
+ ```
117
+
118
+ Use [`debounce`](#checking-for-active-runs-before-triggering) with the same key instead, so triggers within the delay join one run.
119
+
120
+ ### Using a random idempotency key
121
+
122
+ An idempotency key only dedupes triggers that send the same key. A fresh random value on every trigger never matches, so every call creates a new run:
123
+
124
+ ```ts Avoid
125
+ await tasks.trigger("sync-account", payload, {
126
+ // Never matches another trigger, so it dedupes nothing
127
+ idempotencyKey: `sync-${crypto.randomUUID()}`,
128
+ });
129
+ ```
130
+
131
+ Build the key from the thing that should only happen once:
132
+
133
+ ```ts Your backend
134
+ import { tasks } from "@trigger.dev/sdk";
135
+
136
+ // One run per change, even if this code runs again within 30 days
137
+ // (the default key lifetime, set with idempotencyKeyTTL)
138
+ await tasks.trigger("process-change", payload, {
139
+ idempotencyKey: `change-${change.id}`,
140
+ });
141
+ ```
142
+
143
+ <Note>
144
+ Inside a task, a raw string key is scoped to the current run. To dedupe across runs, create it
145
+ with `idempotencyKeys.create(key, { scope: "global" })`. See [Idempotency](/idempotency) for
146
+ scopes and TTLs.
147
+ </Note>
148
+
149
+ ### Polling for a run's status
150
+
151
+ ```ts Avoid
152
+ const handle = await tasks.trigger("generate-report", payload);
153
+
154
+ while (true) {
155
+ const run = await runs.retrieve(handle.id);
156
+ if (run.isCompleted) break;
157
+ await new Promise((resolve) => setTimeout(resolve, 1000));
158
+ }
159
+ ```
160
+
161
+ Inside a task, use [`triggerAndWait()`](/triggering#yourtask-triggerandwait). You don't pay for compute during waits longer than 5 seconds.
162
+
163
+ From your backend, subscribe to the run. The loop ends when the run finishes:
164
+
165
+ ```ts Use realtime
166
+ import { runs, tasks } from "@trigger.dev/sdk";
167
+
168
+ const handle = await tasks.trigger("generate-report", payload);
169
+
170
+ for await (const run of runs.subscribeToRun(handle.id)) {
171
+ console.log(run.status);
172
+ }
173
+ ```
174
+
175
+ ### Polling runs by tag to follow progress
176
+
177
+ Calling `runs.list({ tag })` on a timer to see what a user's or a job's runs are doing repeats the same search every time. Subscribe once instead:
178
+
179
+ ```ts Use realtime
180
+ import { runs } from "@trigger.dev/sdk";
181
+
182
+ const tag = `user:${userId}`;
183
+
184
+ for await (const run of runs.subscribeToRunsWithTag(tag)) {
185
+ console.log(run.id, run.status);
186
+ }
187
+ ```
188
+
189
+ This iterator never completes on its own, so break out of the loop when you're done. In a frontend, use the [React hooks](/realtime/react-hooks/overview).
190
+
191
+ <Note>
192
+ Tag subscriptions only include runs created in the last 24 hours. You can narrow that with the
193
+ `createdAt` option, but not extend it. To follow a specific older run, use
194
+ `runs.subscribeToRun(runId)`.
195
+ </Note>
196
+
197
+ ### Counting runs to measure a backlog
198
+
199
+ Listing `QUEUED` runs to count them gets slower as the backlog grows, which is exactly when you need the number. It can also miss runs: queued runs created before the [time window](#when-you-do-need-runs-list) aren't returned. The queue already tracks the count:
200
+
201
+ ```ts Use queues.retrieve()
202
+ import { queues } from "@trigger.dev/sdk";
203
+
204
+ // A task's default queue is named after the task ID
205
+ const queue = await queues.retrieve({
206
+ type: "task",
207
+ name: "sync-account",
208
+ });
209
+
210
+ console.log(queue.queued, queue.running);
211
+ ```
212
+
213
+ For a queue shared between tasks, use `{ type: "custom", name: "your-queue-name" }`.
214
+
215
+ ## When you do need runs.list()
216
+
217
+ `runs.list()` is the right tool for dashboards, reports, audits, and finding runs to act on in bulk. To keep it fast:
218
+
219
+ - **Pass a time window.** Use the narrowest `period` (or `from`/`to`) that covers what you need, like `period: "1h"`.
220
+ - **Filter by `taskIdentifier`** as well as status or tag.
221
+ - **Stop paginating once you have what you need.** Don't page through every result by default.
222
+ - **Keep it off hot paths.** Don't call it per event, per trigger, or in a tight loop inside a task. If several places need the same answer, cache it.
223
+
224
+ <Warning>
225
+ If you don't pass `period`, `from`, or `to`, only runs created in the last 7 days are searched.
226
+ The window applies to every status, so runs created before it aren't returned even if they're
227
+ still queued or executing. Passing only `to` removes the lower bound, so the search covers every
228
+ run up to that date.
229
+ </Warning>
230
+
231
+ <Note>
232
+ If a query would be too expensive to complete, the API returns a `422` asking you to narrow the
233
+ time window. The SDK doesn't retry it.
234
+ </Note>
@@ -4,6 +4,10 @@ sidebarTitle: "Max duration"
4
4
  description: "Set a maximum duration for a task to run."
5
5
  ---
6
6
 
7
+ Runs have no maximum duration. You set a default `maxDuration` for your project and can override it per task or per run, or set it to `timeout.None` to let a run execute indefinitely.
8
+
9
+ Trigger is built for long running AI agents and workflows and we have seen Trigger.dev users with runs lasting more than six months.
10
+
7
11
  The `maxDuration` parameter sets a maximum compute time limit for tasks. When a task exceeds this duration, it will be automatically stopped. This helps prevent runaway tasks and manage compute resources effectively.
8
12
 
9
13
  You must set a default maxDuration in your `trigger.config.ts` file, which will apply to all tasks unless overridden:
@@ -74,7 +74,7 @@ This means it's safe to call these methods anywhere in your code, and they will
74
74
  undefined.
75
75
  </Note>
76
76
 
77
- These methods also work inside any task lifecycle hook, either attached to the specific task or the global hooks defined in your `trigger.config.ts` file.
77
+ These methods also work inside any task lifecycle hook, either attached to the specific task or registered globally with `tasks.*` (for example in your `init.ts` file).
78
78
 
79
79
  <CodeGroup>
80
80
 
@@ -86,7 +86,7 @@ export const myTask = task({
86
86
  run: async (payload: { message: string }) => {
87
87
  // Your run function work here
88
88
  },
89
- onStart: async () => {
89
+ onStartAttempt: async () => {
90
90
  metadata.set("progress", 0.5);
91
91
  },
92
92
  onSuccess: async () => {
@@ -95,14 +95,11 @@ export const myTask = task({
95
95
  });
96
96
  ```
97
97
 
98
- ```ts trigger.config.ts
99
- import { defineConfig, metadata } from "@trigger.dev/sdk";
98
+ ```ts init.ts
99
+ import { tasks, metadata } from "@trigger.dev/sdk";
100
100
 
101
- export default defineConfig({
102
- project: "proj_1234",
103
- onStart: async () => {
104
- metadata.set("progress", 0.5);
105
- },
101
+ tasks.onStartAttempt(async () => {
102
+ metadata.set("progress", 0.5);
106
103
  });
107
104
  ```
108
105
 
@@ -731,4 +728,4 @@ See the [API reference](/management/runs/retrieve) for more information.
731
728
 
732
729
  ## Size limit
733
730
 
734
- The maximum size of the metadata object is 256KB. If you exceed this limit, the SDK will throw an error. If you are self-hosting Trigger.dev, you can increase this limit by setting the `TASK_RUN_METADATA_MAXIMUM_SIZE` environment variable. For example, to increase the limit to 16KB, you would set `TASK_RUN_METADATA_MAXIMUM_SIZE=16384`.
731
+ The maximum size of the metadata object is 256KB. If an update would exceed this limit, the server rejects it (HTTP 413) and the update is not applied. Inside a task, metadata updates are sent in the background, so the SDK logs a `Failed to flush metadata` error instead of throwing from `metadata.set()` or `metadata.flush()`. If you are self-hosting, you can change the limit with the `TASK_RUN_METADATA_MAXIMUM_SIZE` environment variable (bytes, default 262144). For example, to raise it to 512KB, set `TASK_RUN_METADATA_MAXIMUM_SIZE=524288`.
package/docs/runs.mdx CHANGED
@@ -60,7 +60,7 @@ to complete successfully due to an error in the task code.
60
60
  failed because it exceeded its `maxDuration`.
61
61
 
62
62
  <Icon icon="fire" iconType="solid" color="#E11D48" size={17} /> **Crashed**: The worker process crashed
63
- during execution (likely due to an Out of Memory error) and won’t be retried.
63
+ during execution (likely due to an Out of Memory error) and won’t be retried, unless `retry.outOfMemory.machine` is set to retry on a larger machine. See [Machines](/machines#retrying-with-a-larger-machine).
64
64
 
65
65
  <Icon icon="bug" iconType="solid" color="#E11D48" size={17} /> **System failure**: An unrecoverable system
66
66
  error has occurred.
@@ -98,7 +98,7 @@ import { runs } from "@trigger.dev/sdk";
98
98
 
99
99
  const run = await runs.retrieve("run_1234");
100
100
 
101
- if (run.isCompleted) {
101
+ if (run.isSuccess) {
102
102
  console.log("Run completed successfully");
103
103
  }
104
104
  ```
@@ -111,7 +111,7 @@ These helpers are also available when subscribing to Realtime run updates:
111
111
  import { runs } from "@trigger.dev/sdk";
112
112
 
113
113
  for await (const run of runs.subscribeToRun("run_1234")) {
114
- if (run.isCompleted) {
114
+ if (run.isSuccess) {
115
115
  console.log("Run completed successfully!");
116
116
  break;
117
117
  }
@@ -129,7 +129,7 @@ await yourTask.trigger({ foo: "bar" }, { idempotencyKey: "unique-key" });
129
129
  ```
130
130
 
131
131
  - If a run with the same idempotency key is already in progress, the new trigger will be ignored.
132
- - If the run has already finished, the previous output or error will be returned.
132
+ - If the run completed successfully (or was canceled), the existing run is returned. If it failed or expired, the key is released and triggering again creates a new run.
133
133
 
134
134
  See our [Idempotency docs](/idempotency) for more information.
135
135
 
@@ -253,6 +253,12 @@ Similar to `triggerAndWait()`, the `batchTriggerAndWait()` function lets you bat
253
253
 
254
254
  #### runs.list()
255
255
 
256
+ <Warning>
257
+ `runs.list()` is for browsing, reporting, and bulk operations, not for coordinating runs. Don't
258
+ call it before every trigger or poll it in a loop. See [runs.list() best
259
+ practices](/runs/list-best-practices) for what to use instead.
260
+ </Warning>
261
+
256
262
  List runs in a specific environment. You can filter the runs by status, created at, task identifier, version, and more:
257
263
 
258
264
  ```ts
@@ -290,7 +296,7 @@ import { runs } from "@trigger.dev/sdk";
290
296
  const response = await runs.list({
291
297
  status: ["QUEUED", "EXECUTING"], // Filter by status
292
298
  taskIdentifier: ["my-task", "my-other-task"], // Filter by task identifier
293
- from: new Date("2024-04-01T00:00:00Z"), // Filter by created at
299
+ from: new Date(Date.now() - 24 * 60 * 60 * 1000), // Filter by created at (last 24 hours)
294
300
  to: new Date(),
295
301
  version: "20241127.2", // Filter by deployment version,
296
302
  tag: ["tag1", "tag2"], // Filter by tags
@@ -327,7 +333,7 @@ If you have just triggered a run, you can pass the entire response object to `re
327
333
  import { runs, tasks } from "@trigger.dev/sdk";
328
334
  import type { myTask } from "./trigger/myTask";
329
335
 
330
- const response = await tasks.trigger<typeof myTask>({ foo: "bar" });
336
+ const response = await tasks.trigger<typeof myTask>("my-task", { foo: "bar" });
331
337
  const run = await runs.retrieve(response);
332
338
 
333
339
  console.log(run.payload.foo); // string
@@ -346,14 +352,18 @@ await runs.cancel(runId);
346
352
 
347
353
  #### runs.replay()
348
354
 
349
- Replay a run:
355
+ Replay a run, optionally overriding its payload or options for the new run:
350
356
 
351
357
  ```ts
352
358
  import { runs } from "@trigger.dev/sdk";
353
359
 
354
360
  await runs.replay(runId);
361
+
362
+ await runs.replay(runId, { payload: { userId: "user_456" }, delay: "10m" });
355
363
  ```
356
364
 
365
+ See [Replaying](/replaying#replaying-using-the-sdk) for every option.
366
+
357
367
  #### runs.reschedule()
358
368
 
359
369
  Updates a delayed run with a new delay. Only valid when the run is in the DELAYED state.
@@ -392,5 +402,5 @@ For more on real-time updates, see the [Realtime](/realtime) documentation.
392
402
 
393
403
  ### Triggering runs for undeployed tasks
394
404
 
395
- It's possible to trigger a run for a task that hasn't been deployed yet. The run will enter the "Waiting for deploy" state until the task is deployed. Once deployed, the run will be queued and executed normally.
405
+ It's possible to trigger a run for a task that hasn't been deployed yet. The run will enter the "Pending version" state until the task is deployed. Once deployed, the run will be queued and executed normally.
396
406
  This feature is particularly useful in CI/CD pipelines where you want to trigger tasks before the deployment is complete.
@@ -14,6 +14,7 @@ mode: "wide"
14
14
  | `OTEL_EXPORTER_OTLP_ENDPOINT` | Yes | — | OTel exporter endpoint. Point at: `<webapp-url>/otel` |
15
15
  | **Worker instance** | | | |
16
16
  | `TRIGGER_WORKER_INSTANCE_NAME` | No | random UUID | Worker instance name. Set to `spec.nodeName` on k8s. |
17
+ | `TRIGGER_WORKER_CELL_NAME` | No | — | Name of the worker cluster this supervisor serves. Stamped on each run it starts. |
17
18
  | `TRIGGER_WORKER_HEARTBEAT_INTERVAL_SECONDS` | No | 30 | Worker heartbeat interval (seconds). |
18
19
  | **Workload API settings** | | | |
19
20
  | `TRIGGER_WORKLOAD_API_ENABLED` | No | true | Enable workload API. Runs use this to perform actions. |
@@ -63,6 +64,8 @@ mode: "wide"
63
64
  | **Failed pod handler** | | | |
64
65
  | `FAILED_POD_HANDLER_ENABLED` | No | true | Enable failed pod handler. |
65
66
  | `FAILED_POD_HANDLER_RECONNECT_INTERVAL_MS` | No | 1000 (1s) | Failed pod handler reconnect interval (ms). |
67
+ | `FAILED_POD_HANDLER_WATCH_TIMEOUT_SECONDS` | No | 300 (5m) | How long the server keeps each failed pod watch open (s). |
68
+ | `RUNNER_KILL_REPORTS_ENABLED` | No | true | Fail a run straight away when its pod is killed, as out of memory only if Kubernetes reports `OOMKilled`. |
66
69
  | **Debug** | | | |
67
70
  | `DEBUG` | No | false | Enable debug logs. |
68
71
  | `SEND_RUN_DEBUG_LOGS` | No | false | Send run debug logs to the platform. |
@@ -44,6 +44,9 @@ mode: "wide"
44
44
  | `WHITELISTED_EMAILS` | No | — | Whitelisted emails regex. |
45
45
  | `BLOCKED_EMAIL_DOMAINS` | No | — | Comma-separated email domains that can't sign in or sign up, including their subdomains. |
46
46
  | `LOGIN_RATE_LIMITS_ENABLED` | No | true | Enable rate limiting on magic-link login. |
47
+ | `MAGIC_LINK_SAME_BROWSER_REQUIRED` | No | true | Only accept a magic link opened in the browser that requested it. Set to false to allow opening it on another device.|
48
+ | `TURNSTILE_SITE_KEY` | No | — | Cloudflare Turnstile site key. With `TURNSTILE_SECRET_KEY`, adds a bot check to the magic-link sign-in form. |
49
+ | `TURNSTILE_SECRET_KEY` | No | — | Cloudflare Turnstile secret key, used to verify the check on the server. Turnstile is off unless both keys are set.|
47
50
  | `AUTH_GITHUB_CLIENT_ID` | No | — | GitHub client ID. |
48
51
  | `AUTH_GITHUB_CLIENT_SECRET` | No | — | GitHub client secret. |
49
52
  | `CSP_IMG_SRC_ALLOWLIST` | No | — | Extra hosts the dashboard may load images from, e.g. an SSO or avatar host serving profile images. A comma-separated list of exact origins (`https://sso.example.com`); a wildcard host such as `https://*.example.com` is refused. |
@@ -98,6 +101,8 @@ mode: "wide"
98
101
  | `DEPLOY_REGISTRY_NAMESPACE` | No | trigger | Deploy registry namespace. |
99
102
  | `DEPLOY_REGISTRY_ECR_DEFAULT_REPOSITORY_POLICY` | No | — | Raw IAM policy JSON applied via SetRepositoryPolicy to every ECR repo created by the webapp. Use to grant cross-account pull access to EKS workers when the ECR account is separate from the cluster account. |
100
103
  | `DEPLOY_IMAGE_PLATFORM` | No | linux/amd64 | Deploy image platform, same values as docker `--platform` flag. |
104
+ | `DEPLOY_BASE_IMAGES` | No | — | Base images every deploy must build on, as comma-separated `runtime=image@sha256:<digest>`. See [custom base images](/self-hosting/overview#custom-base-images). |
105
+ | `DEPLOY_BUILD_BASE_IMAGES` | No | — | Build-stage images, in the same format as `DEPLOY_BASE_IMAGES`. See [custom base images](/self-hosting/overview#custom-base-images). |
101
106
  | `DEPLOY_TIMEOUT_MS` | No | 480000 (8m) | Deploy timeout (ms). |
102
107
  | `DEPLOY_QUEUE_TIMEOUT_MS` | No | 900000 (15m) | Deploy queue timeout (ms). |
103
108
  | **Object store (S3)** | | | |
@@ -100,6 +100,32 @@ All fields are optional. Partial overrides are supported:
100
100
  }
101
101
  ```
102
102
 
103
+ ## Custom base images
104
+
105
+ Deploys build on the published `triggerdotdev/node` and `triggerdotdev/bun` Debian images. To require a different base for every deploy to your instance, such as a FIPS-validated or hardened Node image, set `DEPLOY_BASE_IMAGES` on the webapp (and optionally `DEPLOY_BUILD_BASE_IMAGES` for the build stage):
106
+
107
+ ```bash
108
+ DEPLOY_BASE_IMAGES="node-26=registry.example.com/node-fips:26@sha256:<64-character-digest>"
109
+ ```
110
+
111
+ Entries are comma-separated `runtime=image@sha256:<digest>`. The runtimes are `node-22`, `node-24`, `node-26` and `bun`, and every image must be pinned by digest. An invalid value prevents the webapp from starting. Projects with `runtime: "node"` in their config resolve to the current default Node runtime (`node-24` today), so set that key for them. With the Helm chart, set the variables through `webapp.extraEnvVars`.
112
+
113
+ Runtimes with an entry build on that image; the others keep the published images. Deploys from CLI versions that cannot apply the images are rejected with an error asking to upgrade, and so are `--native-build`, `--local-bundle` and `--from-bundle` deploys. This is a self-hosting setting and does not apply to Trigger.dev Cloud.
114
+
115
+ You own a custom base image. It must provide:
116
+
117
+ - `node` on `PATH` at the runtime's major version (for `bun`, both `bun` and `node`, because the final stage starts the app with `dumb-init node`)
118
+ - `busybox`, `ca-certificates`, `dumb-init`, `git` and `openssl`
119
+ - a `node` user (a `bun` user for the Bun runtime)
120
+ - glibc, so native modules built in the build stage load at runtime
121
+
122
+ A `DEPLOY_BUILD_BASE_IMAGES` image needs everything the base image provides, plus `python3`, `make` and `g++`. Build extensions that add image instructions are replayed on it. Without an entry, the build stage uses the published build image, except for projects whose build extensions add image instructions: those build from your base image and install the toolchain with `apt-get`, so that base must be Debian-based. To avoid the published images entirely, set both variables.
123
+
124
+ <Warning>
125
+ `image.pkgs` and build extensions that run `apt-get` (such as `aptGet` and `playwright`) assume a
126
+ Debian base. On other distributions, install those packages in your base image instead.
127
+ </Warning>
128
+
103
129
  ## Community support
104
130
 
105
131
  It's dangerous to go alone! Join the self-hosting channel on our [Discord server](https://discord.gg/NQTxt5NA7s).
package/docs/tags.mdx CHANGED
@@ -118,3 +118,9 @@ for await (const run of runs.list({ tag: "user_123456", status: ["COMPLETED"] })
118
118
  console.log(run.id, run.taskIdentifier, run.finishedAt, run.tags);
119
119
  }
120
120
  ```
121
+
122
+ <Tip>
123
+ To follow runs with a tag as they change, use
124
+ [`runs.subscribeToRunsWithTag()`](/realtime/backend/subscribe) instead of polling `runs.list()`.
125
+ See [runs.list() best practices](/runs/list-best-practices).
126
+ </Tip>
@@ -45,7 +45,7 @@ export const firstScheduledTask = schedules.task({
45
45
  //this can be undefined if you didn't provide one
46
46
  console.log(payload.externalId); //is a string or undefined
47
47
 
48
- //the next 5 dates this task is scheduled to run
48
+ //the next 10 dates this task is scheduled to run
49
49
  console.log(payload.upcoming); //is an array of Date objects
50
50
  },
51
51
  });
@@ -58,14 +58,14 @@ You can see from the comments that the payload has several useful properties:
58
58
  - `timezone` - the timezone the schedule was registered with, defaults to "UTC". In IANA format, e.g. "America/New_York".
59
59
  - `scheduleId` - the id of the schedule that triggered the task
60
60
  - `externalId` - the external id you (optionally) provided when creating the schedule
61
- - `upcoming` - the next 5 times the task is scheduled to run
61
+ - `upcoming` - the next 10 times the task is scheduled to run
62
62
 
63
63
  <Note>
64
64
  This task will NOT get triggered on a schedule until you attach a schedule to it. Read on for how
65
65
  to do that.
66
66
  </Note>
67
67
 
68
- Like all tasks they don't have timeouts, they should be placed inside a [/trigger folder](/config/config-file), and you [can configure them](/tasks/overview#defining-a-task).
68
+ Like all tasks they have a [maxDuration](/runs/max-duration), they should be placed inside a [/trigger folder](/config/config-file), and you [can configure them](/tasks/overview#defining-a-task).
69
69
 
70
70
  You can set a [TTL](/runs#time-to-live-ttl) on a scheduled task to automatically expire runs that aren't dequeued in time. This is useful when a schedule fires while the previous run is still executing - rather than queueing up stale runs, they'll expire:
71
71
 
@@ -142,7 +142,7 @@ To use imperative schedules you need to do two things:
142
142
 
143
143
  ## Spreading runs with windows
144
144
 
145
- By default a schedule runs at its exact cron time. When many schedules share the same cron pattern, such as a daily `0 9 * * *`, they all fire at the same time and load your downstream systems as well as ours. A **window** spreads those runs out: each schedule is assigned a stable time within the window after its cron time, so the load is smoothed while each schedule keeps firing at a predictable, repeatable moment.
145
+ When many schedules share the same cron pattern, such as a daily `0 9 * * *`, they all fire at the same time and load your downstream systems as well as ours. A **window** spreads those runs out: each schedule is assigned a stable time within the window after its cron time, so the load is smoothed while each schedule keeps firing at a predictable, repeatable moment. New schedules use a [60-minute window by default](#default-window).
146
146
 
147
147
  The assigned time is deterministic. A given schedule always lands at the same offset for a given interval, so runs don't jump around between occurrences. And you can always see exactly when the next run will start.
148
148
 
@@ -151,7 +151,7 @@ Set a `window` as either:
151
151
  - **An absolute duration** in whole minutes or hours, up to 24 hours: `"30m"`, `"2h"`, `"24h"`. Absolute windows are capped at the next cron time, so a run is never delayed past its following occurrence.
152
152
  - **A percentage** of the interval between runs: `"30%"`, `"100%"`. A `"50%"` window on an hourly schedule spreads runs across the first 30 minutes of each hour.
153
153
 
154
- Set `"0m"` (or `"0%"`) for no spreading — the run fires at its exact cron time.
154
+ Set `"0m"` (or `"0%"`) for no spreading — the run fires at its exact cron time. This also overrides the [default window](#default-window).
155
155
 
156
156
  Declarative schedules set the window on the `cron` object:
157
157
 
@@ -184,6 +184,12 @@ const createdSchedule = await schedules.create({
184
184
  is the assigned time the run will actually start.
185
185
  </Note>
186
186
 
187
+ ### Default window
188
+
189
+ New schedules get a default 60-minute window unless you set one, so each run starts at a stable time within the hour after its cron time. Like any absolute window, it's capped at the next cron time: a schedule that runs every 15 minutes is spread across those 15 minutes.
190
+
191
+ To opt a new schedule out of the default window, set `window: "0m"`.
192
+
187
193
  ### Free-plan minimum window
188
194
 
189
195
  Schedules created while an organization is on a free plan run no more than once per hour and use a minimum 60-minute window.
@@ -746,6 +746,12 @@ await myTask.batchTrigger([{ payload: { some: "data" }, options: { delay: "1h" }
746
746
  manually, convert Dates to ISO strings first (e.g., `date.toISOString()`).
747
747
  </Note>
748
748
 
749
+ <Tip>
750
+ A `delay` doesn't merge triggers: each trigger still creates its own run. To collapse a burst of
751
+ triggers into one run, use [`debounce`](#debounce). See [runs.list() best
752
+ practices](/runs/list-best-practices#using-a-fixed-delay-to-space-out-triggers).
753
+ </Tip>
754
+
749
755
  ### `ttl`
750
756
 
751
757
  You can set a TTL (time to live) when triggering a task, which will automatically expire the run if it hasn't started within the specified time. This is useful for ensuring that a run doesn't get stuck in the queue for too long.
@@ -814,6 +820,8 @@ export const myTask = task({
814
820
  });
815
821
  ```
816
822
 
823
+ Build the key from the work being done, like an order or event ID. A random value on every trigger never matches, so it never dedupes. See [runs.list() best practices](/runs/list-best-practices#using-a-random-idempotency-key).
824
+
817
825
  For more information, see our [Idempotency](/idempotency) documentation.
818
826
 
819
827
  ### `idempotencyKeyTTL`