Skip to content
7 changes: 7 additions & 0 deletions .changeset/max-compute-seconds.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@trigger.dev/core": patch
"@trigger.dev/sdk": patch
"trigger.dev": patch
---

Add `maxComputeSeconds` as the user-facing replacement for `maxDuration` on `defineConfig`, task definitions, and trigger options. The new name makes the unit (compute-time seconds) unambiguous at the call site. `maxDuration` is JSDoc-deprecated and still accepted; if both are set, `maxComputeSeconds` wins. The `init` templates have been updated to use the new name.
9 changes: 7 additions & 2 deletions packages/cli-v3/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,11 +341,16 @@ function validateConfig(config: TriggerConfig, warn = true) {
config.build.extensions.push(adaptResolveEnvVarsToSyncEnvVarsExtension(resolveEnvVarsFn));
}

if (!config.maxDuration) {
// Resolve maxComputeSeconds → maxDuration so plain-object exports that bypass
// defineConfig() still work. This mirrors the resolution defineConfig() applies
// at the SDK boundary; downstream CLI/runtime code only reads `maxDuration`.
const resolvedMaxDuration = config.maxComputeSeconds ?? config.maxDuration;
if (!resolvedMaxDuration) {
throw new Error(
`The "maxDuration" trigger.config option is now required, and must be at least 5 seconds.`
`The "maxComputeSeconds" trigger.config option is now required, and must be at least 5 seconds.`
);
}
config.maxDuration = resolvedMaxDuration;
Comment on lines +347 to +353
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Lower-bound validation is missing for maxComputeSeconds.

On Line 348, if (!resolvedMaxDuration) only rejects falsy values. Values 1..4 pass, even though the error on Line 350 states the minimum is 5 seconds.

Proposed fix
-  const resolvedMaxDuration = config.maxComputeSeconds ?? config.maxDuration;
-  if (!resolvedMaxDuration) {
+  const resolvedMaxDuration = config.maxComputeSeconds ?? config.maxDuration;
+  if (
+    typeof resolvedMaxDuration !== "number" ||
+    !Number.isFinite(resolvedMaxDuration) ||
+    resolvedMaxDuration < 5
+  ) {
     throw new Error(
       `The "maxComputeSeconds" trigger.config option is now required, and must be at least 5 seconds.`
     );
   }
   config.maxDuration = resolvedMaxDuration;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const resolvedMaxDuration = config.maxComputeSeconds ?? config.maxDuration;
if (!resolvedMaxDuration) {
throw new Error(
`The "maxDuration" trigger.config option is now required, and must be at least 5 seconds.`
`The "maxComputeSeconds" trigger.config option is now required, and must be at least 5 seconds.`
);
}
config.maxDuration = resolvedMaxDuration;
const resolvedMaxDuration = config.maxComputeSeconds ?? config.maxDuration;
if (
typeof resolvedMaxDuration !== "number" ||
!Number.isFinite(resolvedMaxDuration) ||
resolvedMaxDuration < 5
) {
throw new Error(
`The "maxComputeSeconds" trigger.config option is now required, and must be at least 5 seconds.`
);
}
config.maxDuration = resolvedMaxDuration;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli-v3/src/config.ts` around lines 347 - 353, The current check
using resolvedMaxDuration only rejects falsy values and allows values 1..4;
update the validation around resolvedMaxDuration (derived from
config.maxComputeSeconds) to ensure it is a number >= 5 before assigning to
config.maxDuration, and if it is missing or less than 5 throw the same Error
with the message that "maxComputeSeconds" is required and must be at least 5
seconds; adjust the branch that currently throws to cover both undefined/null
and values < 5 so resolvedMaxDuration is only assigned to config.maxDuration
when valid.


if (config.runtime && config.runtime === "bun") {
warn &&
Expand Down
4 changes: 2 additions & 2 deletions packages/cli-v3/templates/examples/schedule.mjs.template
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ export const firstScheduledTask = schedules.task({
id: "first-scheduled-task",
// Every hour
cron: "0 * * * *",
// Set an optional maxDuration to prevent tasks from running indefinitely
maxDuration: 300, // Stop executing after 300 secs (5 mins) of compute
// Set an optional maxComputeSeconds to prevent tasks from running indefinitely
maxComputeSeconds: 300, // Stop executing after 300 secs (5 mins) of compute
run: async (payload, { ctx }) => {
// The payload contains the last run timestamp that you can use to check if this is the first run
// And calculate the time since the last run
Expand Down
4 changes: 2 additions & 2 deletions packages/cli-v3/templates/examples/schedule.ts.template
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ export const firstScheduledTask = schedules.task({
id: "first-scheduled-task",
// Every hour
cron: "0 * * * *",
// Set an optional maxDuration to prevent tasks from running indefinitely
maxDuration: 300, // Stop executing after 300 secs (5 mins) of compute
// Set an optional maxComputeSeconds to prevent tasks from running indefinitely
maxComputeSeconds: 300, // Stop executing after 300 secs (5 mins) of compute
run: async (payload, { ctx }) => {
// The payload contains the last run timestamp that you can use to check if this is the first run
// And calculate the time since the last run
Expand Down
4 changes: 2 additions & 2 deletions packages/cli-v3/templates/examples/simple.mjs.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { logger, task, wait } from "@trigger.dev/sdk/v3";

export const helloWorldTask = task({
id: "hello-world",
// Set an optional maxDuration to prevent tasks from running indefinitely
maxDuration: 300, // Stop executing after 300 secs (5 mins) of compute
// Set an optional maxComputeSeconds to prevent tasks from running indefinitely
maxComputeSeconds: 300, // Stop executing after 300 secs (5 mins) of compute
run: async (payload, { ctx }) => {
logger.log("Hello, world!", { payload, ctx });

Expand Down
4 changes: 2 additions & 2 deletions packages/cli-v3/templates/examples/simple.ts.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { logger, task, wait } from "@trigger.dev/sdk/v3";

export const helloWorldTask = task({
id: "hello-world",
// Set an optional maxDuration to prevent tasks from running indefinitely
maxDuration: 300, // Stop executing after 300 secs (5 mins) of compute
// Set an optional maxComputeSeconds to prevent tasks from running indefinitely
maxComputeSeconds: 300, // Stop executing after 300 secs (5 mins) of compute
run: async (payload: any, { ctx }) => {
logger.log("Hello, world!", { payload, ctx });

Expand Down
4 changes: 2 additions & 2 deletions packages/cli-v3/templates/trigger.config.mjs.template
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ export default defineConfig({
project: "${projectRef}",
runtime: "${runtime}",
logLevel: "log",
// The max compute seconds a task is allowed to run. If the task run exceeds this duration, it will be stopped.
// The max compute seconds a task is allowed to run. If the run exceeds this, it will be stopped.
// You can override this on an individual task.
// See https://trigger.dev/docs/runs/max-duration
maxDuration: 3600,
maxComputeSeconds: 3600,
retries: {
enabledInDev: true,
default: {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli-v3/templates/trigger.config.ts.template
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ export default defineConfig({
project: "${projectRef}",
runtime: "${runtime}",
logLevel: "log",
// The max compute seconds a task is allowed to run. If the task run exceeds this duration, it will be stopped.
// The max compute seconds a task is allowed to run. If the run exceeds this, it will be stopped.
// You can override this on an individual task.
// See https://trigger.dev/docs/runs/max-duration
maxDuration: 3600,
maxComputeSeconds: 3600,
retries: {
enabledInDev: true,
default: {
Expand Down
14 changes: 13 additions & 1 deletion packages/core/src/v3/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,16 +165,28 @@ export type TriggerConfig = {
*/
logLevel?: LogLevel;

/**
* The maximum duration in compute-time **seconds** that a task run is allowed to run. If the task run exceeds this duration, it will be stopped.
*
* Minimum value is 5 seconds.
*
* Setting this value will affect all tasks in the project. You can override it on a per-task basis.
*
* @see https://trigger.dev/docs/tasks/overview#maxcomputeseconds-option
*/
maxComputeSeconds?: number;

/**
* The maximum duration in compute-time seconds that a task run is allowed to run. If the task run exceeds this duration, it will be stopped.
*
* Minimum value is 5 seconds
*
* Setting this value will effect all tasks in the project.
*
* @deprecated Use `maxComputeSeconds` instead — same semantics, clearer unit. If both are set, `maxComputeSeconds` wins.
* @see https://trigger.dev/docs/tasks/overview#maxduration-option
*/
maxDuration: number;
maxDuration?: number;

/**
* Set a default time-to-live (TTL) for all task runs in the project. If a run is not executed within this time, it will be removed from the queue and never execute.
Expand Down
10 changes: 9 additions & 1 deletion packages/core/src/v3/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,9 @@ export class MaxDurationExceededError extends Error {
public readonly maxDurationInSeconds: number,
public readonly elapsedTimeInSeconds: number
) {
super(`Run exceeded maximum compute time (maxDuration) of ${maxDurationInSeconds} seconds`);
super(
`Run exceeded maximum compute time (maxComputeSeconds) of ${maxDurationInSeconds} seconds`
);

this.name = "MaxDurationExceededError";
}
Expand Down Expand Up @@ -736,6 +738,12 @@ const prettyInternalErrors: Partial<
href: links.docs.troubleshooting.uncaughtException,
},
},
MAX_DURATION_EXCEEDED: {
link: {
name: "How to set maxComputeSeconds (maxDuration)",
href: links.docs.maxDuration,
},
},
};

const getPrettyTaskRunError = (code: TaskRunInternalError["code"]): TaskRunInternalError => {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/v3/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const links = {
personalAccessToken:
"https://trigger.dev/docs/github-actions#creating-a-personal-access-token",
},
maxDuration: "https://trigger.dev/docs/runs/max-duration",
},
site: {
home: "https://trigger.dev",
Expand Down
20 changes: 20 additions & 0 deletions packages/core/src/v3/types/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,19 @@ type CommonTaskOptions<
}
| MachinePresetName;

/**
* The maximum duration in compute-time **seconds** that a task run is allowed to run. If the task run exceeds this duration, it will be stopped.
*
* Minimum value is 5 seconds.
*/
maxComputeSeconds?: number;

/**
* The maximum duration in compute-time seconds that a task run is allowed to run. If the task run exceeds this duration, it will be stopped.
*
* Minimum value is 5 seconds
*
* @deprecated Use `maxComputeSeconds` instead — same semantics, clearer unit. If both are set, `maxComputeSeconds` wins.
*/
maxDuration?: number;

Expand Down Expand Up @@ -876,12 +885,23 @@ export type TriggerOptions = {
*/
metadata?: Record<string, SerializableJson>;

/**
* The maximum duration in compute-time **seconds** that a task run is allowed to run. If the task run exceeds this duration, it will be stopped.
*
* This will override the task's `maxComputeSeconds` (or the legacy `maxDuration`).
*
* Minimum value is 5 seconds.
*/
maxComputeSeconds?: number;

/**
* The maximum duration in compute-time seconds that a task run is allowed to run. If the task run exceeds this duration, it will be stopped.
*
* This will override the task's maxDuration.
*
* Minimum value is 5 seconds
*
* @deprecated Use `maxComputeSeconds` instead — same semantics, clearer unit. If both are set, `maxComputeSeconds` wins.
*/
maxDuration?: number;

Expand Down
29 changes: 29 additions & 0 deletions packages/trigger-sdk/src/v3/config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, it, expect } from "vitest";
import { defineConfig } from "./config.js";

describe("defineConfig - maxComputeSeconds", () => {
it("uses maxComputeSeconds when only maxComputeSeconds is set", () => {
const cfg = defineConfig({ project: "p", maxComputeSeconds: 600 });
expect(cfg.maxDuration).toBe(600);
});

it("uses maxDuration when only maxDuration is set", () => {
const cfg = defineConfig({ project: "p", maxDuration: 600 });
expect(cfg.maxDuration).toBe(600);
});

it("prefers maxComputeSeconds when both are set", () => {
const cfg = defineConfig({ project: "p", maxComputeSeconds: 600, maxDuration: 9999 });
expect(cfg.maxDuration).toBe(600);
});

it("leaves maxDuration unset when neither is provided", () => {
const cfg = defineConfig({ project: "p" });
expect(cfg.maxDuration).toBeUndefined();
});

it("strips maxComputeSeconds from the returned config", () => {
const cfg = defineConfig({ project: "p", maxComputeSeconds: 600 });
expect((cfg as { maxComputeSeconds?: number }).maxComputeSeconds).toBeUndefined();
});
});
10 changes: 9 additions & 1 deletion packages/trigger-sdk/src/v3/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@ export type {
} from "@trigger.dev/core/v3";

export function defineConfig(config: TriggerConfig): TriggerConfig {
return config;
// `maxComputeSeconds` is the new name for `maxDuration`. If both are set, the new
// name wins. Internally the SDK and platform still read `maxDuration`, so we
// collapse the two fields here at the user-facing boundary.
const { maxComputeSeconds, maxDuration, ...rest } = config;
const resolved = maxComputeSeconds ?? maxDuration;
return {
...rest,
...(resolved !== undefined ? { maxDuration: resolved } : {}),
};
}

export type { TriggerConfig };
20 changes: 20 additions & 0 deletions packages/trigger-sdk/src/v3/maxComputeSeconds.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, it, expect } from "vitest";
import { resolveMaxComputeSeconds } from "./maxComputeSeconds.js";

describe("resolveMaxComputeSeconds", () => {
it("returns maxComputeSeconds when only maxComputeSeconds is set", () => {
expect(resolveMaxComputeSeconds({ maxComputeSeconds: 300 })).toBe(300);
});

it("returns maxDuration when only maxDuration is set", () => {
expect(resolveMaxComputeSeconds({ maxDuration: 300 })).toBe(300);
});

it("prefers maxComputeSeconds when both are set", () => {
expect(resolveMaxComputeSeconds({ maxComputeSeconds: 300, maxDuration: 999 })).toBe(300);
});

it("returns undefined when neither is set", () => {
expect(resolveMaxComputeSeconds({})).toBeUndefined();
});
});
13 changes: 13 additions & 0 deletions packages/trigger-sdk/src/v3/maxComputeSeconds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Collapse the user-facing `maxComputeSeconds` (new name) and `maxDuration` (deprecated)
* into a single value. If both are provided, `maxComputeSeconds` wins.
*
* Internal SDK/CLI/platform code only reads `maxDuration`, so all call sites that
* accept user input should funnel through this helper before forwarding the value.
*/
export function resolveMaxComputeSeconds(input: {
maxComputeSeconds?: number;
maxDuration?: number;
}): number | undefined {
return input.maxComputeSeconds ?? input.maxDuration;
}
Loading
Loading