1 new feature, 10 improvements, 4 bug fixes, and 18 server changes.
Highlights
Concurrency limits, separated from queues
Concurrency used to be a property of the queue. It is now its own option on the task, and the simple case is a straight swap:
// Beforeexport const summarize = task({ id: "summarize", queue: { concurrencyLimit: 5 }, run: async (payload) => {},});// Afterexport const summarize = task({ id: "summarize", concurrency: { total: 5 }, run: async (payload) => {},});
Per-tenant limits, now with a total
When runs were triggered with a concurrencyKey, the old single number became a per-key limit, and there was no way to also cap the task as a whole. Ten active tenants under a limit of 1 could run ten at once, a thousand could run a thousand. The new shape names both bounds, and you can set them together:
// Before: 1 per tenant, no ceiling on the totalexport const generateReport = task({ id: "generate-report", queue: { concurrencyLimit: 1 }, run: async (payload) => {},});// After: 1 per tenant, and at most 10 across all tenantsexport const generateReport = task({ id: "generate-report", concurrency: { perKey: 1, total: 10 }, run: async (payload) => {},});
perKey caps each concurrencyKey pool, with runs triggered without a key sharing one pool. total caps every run of the task together, keyed or not.
More than one limit per task
Sharing a limit across tasks used to mean putting them on the same queue, which also merged their ordering. Now you declare a named limit with concurrencyLimit() and give it to every task that should share it. Each task keeps its own queue. A task can hold up to two named limits plus one inline shape, and a run starts only when every limit it holds has capacity:
import { concurrencyLimit, task } from "@trigger.dev/sdk";// at most 25 concurrent runs across every task that holds this limitexport const openaiLimit = concurrencyLimit({ name: "openai", total: 25 });export const generateSummary = task({ id: "generate-summary", // at most 5 of this task at once, and also counts towards the shared openai limit concurrency: [{ total: 5 }, openaiLimit], run: async (payload) => {},});export const generateTitle = task({ id: "generate-title", concurrency: openaiLimit, run: async (payload) => {},});
A named limit with only perKey caps each tenant across every task that holds it, which is how you cap a tenant's usage of your whole system rather than one task. A trigger call can swap a run's named limits with its own concurrency option.
The new Concurrency docs walk through the use cases: capping a task, sharing a limit between tasks, per-tenant limits, a per-tenant cap across many tasks, a global cap on a shared resource, and switching limits at trigger time.
The queue-level concurrencyLimit option keeps working unchanged and is deprecated in favor of concurrency. queues.overrideConcurrencyLimit and queues.resetConcurrencyLimit are deprecated too and only act on queues that still carry their own limit.
Manage limits at runtime with the new concurrencyLimits namespace. list() and retrieve(name) report each limit's bounds plus its live running and queued counts. override(name, { perKey, total }) changes only the bounds you pass, and reset(name) restores the declared values. A task's inline limit is addressed as task/<task-id>. Pause and resume a limit without touching its bounds:
import { concurrencyLimits } from "@trigger.dev/sdk";await concurrencyLimits.pause("openai");await concurrencyLimits.resume("openai");
Queue reads (queues.list() and queues.retrieve()) now include a version field. V1 queues keep their own concurrencyLimit. V2 queues, declared with concurrency, report concurrencyLimit as null and manage limits through concurrencyLimits.
Enforcement happens server-side. Trigger.dev Cloud enforces these limits today; self-hosted servers need the v4.7.0 image. Older servers accept the option without enforcing it. (ea97581, fa94feb, 414e5a2)
Improvements
- Chat agents can now scope concurrency per session. Pass
concurrencyKey(for example, a chat ID or tenant ID) and trigger-time named limits viatriggerConfig.concurrencywhen starting a session fromchat.createStartSessionAction,AgentChat, or a handover. Keys are never defaulted, so a session without one shares the task's keyless pool. (ea97581) - Chat streams now report
Stream stalled: no records receivedafter five retries of a connected stream that sends no records. Network failures and browser wakeups still recover automatically. Healthy tool calls with no records for about six minutes also hit this limit. Watch subscriptions remain unlimited, and caller cancellation still closes cleanly. (#4949) - Webhook verifier artifacts can now declare the provider's response contract as data: a handshake
respondStatus, status codes for accepted deliveries and rejected signatures, and a GET verification flow (getHandshake) for providers that confirm a callback URL with a challenge. HMAC verifiers can read the timestamp from a body field, and the dashboard's test-send re-signs a recorded sample as of now so it passes the provider's replay window. (5f54fb2) - Added an optional
onSettledcallback toTriggerChatTransport.sendAction()so callers can confirm their action's input was processed, independently of whether the response stream closes. (#4956) - Chat agents now return to a durable wait when a session wake does not deliver a matching message. Resumed runs no longer stay active until their maximum duration, and the configured turn timeout survives repeated wakes. (
e6ec2c4) - Steering messages now remain in context across agent steps and keep their original position in saved conversations, including custom response data written between steps. (
5619acf) - Set up a new Trigger.dev account from the CLI. Login can prefill an email address, save your name after authorization, and resume later.
initcan create your first organization and project before scaffolding the app. (fb3b26d) - Added a
submit_feedbackMCP tool so coding agents can report a confusing tool error, a docs mismatch, or a missing capability without you filing it by hand. Turn it off with--skip-telemetryorTRIGGER_TELEMETRY_DISABLED; the tool is hidden while telemetry is off. (9d38ff5) trigger promotenow accepts environment API keys supplied throughTRIGGER_ACCESS_TOKEN. Environment API key commands use the saved profile API URL when you pass no override. (562c943)- Zod 4
z.date()fields now convert to date-time strings in JSON Schema without weakening validation for other unsupported types. MCP tool discovery no longer fails when a tool input schema contains a date. (f8babdf)
Bug fixes
- Fixed stale and empty project environment values in
trigger dev, and added support for empty values insyncEnvVars(). (f384e83) - Fixed warm starts silently failing for deployments built with 4.6.0 to 4.6.3 in projects that resolve
zodto a 3.x release. The runner could not parse the run handed to it by the warm-start service and exited, leaving the run waiting until the platform redrove it a few minutes later and started it cold. Redeploy to pick up the fix. (#4972) - Fixed a ~6-second delay between a task finishing and its run completing, and a ~31-second delay when cancelling a run, in projects that use zod 4.4 or newer. Run cost and billed usage were not affected. (#4980)
- Fixed Windows deploys failing at indexing with
Cannot find moduleon a percent-encoded path when the project directory contains spaces or non-ASCII characters. (b32c1d1)
Server changes
These changes are included in the v4.7.0 Docker image and are already live on Trigger.dev Cloud:
- API rate limit usage is now recorded per environment in the
metricstable asapi.rate_limit.allowed,api.rate_limit.denied,api.rate_limit.remaining_min,api.rate_limit.limit.per_second, andapi.rate_limit.limit.burst, so you can chart requests against your limit and 429s over time on the Query page and dashboards. - Archive queues you no longer use from the Concurrency page. Archived queues are hidden from the list and no longer count towards allocated concurrency.
- Automatically archive preview branches after a configurable period without deployments, with protected branch names and a preview of affected branches.
- Download a session's original saved transcript file from the session inspector.
- Organization Owners and Admins can now choose, in Settings under Support Access, whether Trigger.dev support can open their dashboard directly or only after an Owner or Admin approves a request. Each approval lasts 7 days.
- Allocating extra concurrency to environments now requires billing permissions, the same as purchasing it. Allocation changes are applied atomically, so simultaneous edits can no longer exceed your purchased concurrency.
- Changing your account email now sends a confirmation link to the new address, and the change only takes effect once that link is opened. Requesting a magic link no longer creates an account until the link is used.
- Removed the organization-wide Node.js 21 deprecation banner while keeping runtime upgrade guidance in Projects settings.
- Creating a project now asks only for its name.
- Batch waits no longer remain suspended when batch completion is briefly interrupted.
- Batch-and-wait requests are rejected when the parent run belongs to another environment.
- Dashboard pages now stay visible during network interruptions, with a disconnected banner and a Refresh button instead of a full-page error.
- Deployments can replace declarative schedules at the schedule limit when the resulting set stays within quota.
- "Deploy now" now tells you when the branch doesn't exist on GitHub instead of showing a generic error, and a first deployment no longer flags a harmless build-cache message as an error.
- Support for empty-string environment variable values across the dashboard, API, and Vercel sync, behind a feature flag that is disabled by default.
- Fixed a bug where a burst of telemetry could leave OpenTelemetry ingest rejecting every batch for a long time. Batches that wait too long are now dropped individually instead of restarting the processing workers, so ingest recovers as soon as the burst passes.
- Scheduled task "Last run" times stay stable across unchanged deployments and align with configured schedule windows.
- Team members and pending invites on the organization Team page are now listed in alphabetical order.
How to upgrade
Update the trigger.dev/* packages to v4.7.0 using your package manager:
npx trigger.dev@latest update # npmpnpm dlx trigger.dev@latest update # pnpmyarn dlx trigger.dev@latest update # yarnbunx trigger.dev@latest update # bun
Self-hosted users: update your Docker image to ghcr.io/triggerdotdev/trigger.dev:v4.7.0.




