Skip to content

feat(sdk,core,webapp,run-engine): declare queue gates on tasks and triggers - #4827

Open
matt-aitken wants to merge 28 commits into
feat/queue-gates-enginefrom
feat/queue-gates-contract
Open

feat(sdk,core,webapp,run-engine): declare queue gates on tasks and triggers#4827
matt-aitken wants to merge 28 commits into
feat/queue-gates-enginefrom
feat/queue-gates-contract

Conversation

@matt-aitken

@matt-aitken matt-aitken commented Aug 29, 2026

Copy link
Copy Markdown
Member

Summary

Stacked on #4826. Adds the concurrency option to task(): an inline shape caps the task itself, and concurrencyLimit() declares a named limit any task can hold (up to two named limits per task, shared across every holder). A trigger call can switch a run's named limits with its own concurrency option; the task's inline limit always applies.

import { concurrencyLimit, task } from "@trigger.dev/sdk";

export const openaiLimit = concurrencyLimit({ name: "openai", total: 25 });

export const generateSummary = task({
  id: "generate-summary",
  concurrency: [{ perKey: 1, total: 5 }, openaiLimit],
  run: async (payload) => {},
});

await generateSummary.trigger(payload, { concurrency: ["openai"] });

perKey caps each concurrencyKey pool (runs without a key share one pool) and total caps every run holding the limit, keys or not. Queues stay pure ordering: the queue-level concurrencyLimit option keeps working unchanged and is deprecated in favor of concurrency. Limit names use a queue-safe charset (letters, numbers, underscores, hyphens; 1-122 characters), enforced by the factory, at deploy time and at trigger time, so distinct names can never merge after sanitization.

The runtime management surface (the concurrencyLimits namespace: list, retrieve, override, reset) rides this contract; its server side lands higher in the stack.

Design

The SDK normalizes concurrency into the task manifest (an inline shape plus named-limit references); the deploy compiler materializes named limits as dedicated queue rows and compiles inline limits into the task's own queue or an anonymous limit, so the trigger path keeps its current query count. Trigger-time names resolve server-side into the run's gate list, which is stored on the run (replays reuse it) and enforced by the engine mechanism from #4826. A run holds at most four gates end to end: three requested plus the task's anonymous inline-limit gate, so replays survive a task gaining an inline limit after the run was created.

Also renames the public queue option from totalConcurrencyLimit to combinedConcurrencyLimit (introduced lower in this stack, renamed here before anything ships). Engine internals and storage keep their existing names.

@changeset-bot

changeset-bot Bot commented Aug 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f4cebc9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 27 packages
Name Type
@trigger.dev/core Patch
@trigger.dev/sdk Patch
@trigger.dev/react-hooks Patch
@trigger.dev/build Patch
trigger.dev Patch
@trigger.dev/python Patch
@trigger.dev/redis-worker Patch
@trigger.dev/schema-to-json Patch
@internal/clickhouse Patch
@internal/llm-model-catalog Patch
@internal/metrics-pipeline Patch
@trigger.dev/rbac Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/run-store Patch
@internal/schedule-engine Patch
@internal/tracing Patch
@internal/webhook-engine Patch
@internal/webhook-sources Patch
@internal/dashboard-agent Patch
@internal/cache Patch
@trigger.dev/rsc Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer Patch
@trigger.dev/sso Patch
@internal/testcontainers Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: ea8833e5-9f69-4027-b61d-9aed754dc5ff

📥 Commits

Reviewing files that changed from the base of the PR and between 894ac6e and 499479c.

📒 Files selected for processing (1)
  • packages/core/src/v3/schemas/build.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.

📜 Recent review details
⏰ Context from checks skipped due to timeout. (50)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (9, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (19, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (20, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (15, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (24, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (21, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (12, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (16, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (11, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (18, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (22, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (4, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (13, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (8, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (23, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (10, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (17, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (7, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (14, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (2, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (6, 24)
  • GitHub Check: sdk-compat / Node.js 24.18 (warp-ubuntu-latest-x64-4x)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (1, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (3, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (5, 24)
  • GitHub Check: e2e / 🧪 CLI v3 tests (warp-ubuntu-latest-x64-4x - npm)
  • GitHub Check: sdk-compat / Node.js 20.20 (warp-ubuntu-latest-x64-4x)
  • GitHub Check: sdk-compat / Node.js 22.23 (warp-ubuntu-latest-x64-4x)
  • GitHub Check: sdk-compat / Node.js 26.4 (warp-ubuntu-latest-x64-4x)
  • GitHub Check: packages / 🧪 Unit Tests: Packages (1, 3)
  • GitHub Check: e2e / 🧪 CLI v3 tests (warp-ubuntu-latest-x64-4x - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (warp-windows-latest-x64-8x - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (warp-windows-latest-x64-8x - npm)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: obsmap / 🧪 Unit Tests: Observability Map
  • GitHub Check: packages / 🧪 Unit Tests: Packages (2, 3)
  • GitHub Check: internal / 🧪 Unit Tests: Internal
  • GitHub Check: runops-guard / runops-guard
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp (1, 2)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: packages / 🧪 Unit Tests: Packages (3, 3)
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp (2, 2)
  • GitHub Check: fk-cascade-guard / fk-cascade-guard
  • GitHub Check: code-quality / code-quality
  • GitHub Check: audit
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: audit
  • GitHub Check: Build and publish previews
🧰 Additional context used
📓 Path-based instructions (8)
**Import subpaths only** (never root).

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • packages/core/src/v3/schemas/build.ts
**Prefer static imports over dynamic imports.**

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • packages/core/src/v3/schemas/build.ts
Add crumbs as you write code — not just when debugging.

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • packages/core/src/v3/schemas/build.ts
Use zod for validation in packages/core and apps/webapp

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • packages/core/src/v3/schemas/build.ts
Never import the root package (`@trigger.dev/core`).

📄 CodeRabbit inference engine (packages/core/CLAUDE.md)

Files:

  • packages/core/src/v3/schemas/build.ts
Use function declarations instead of default exports

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • packages/core/src/v3/schemas/build.ts
Use types over interfaces for TypeScript Avoid using enums; prefer string unions or const objects instead

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • packages/core/src/v3/schemas/build.ts
When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs Do not use high-cardinality attributes in OTEL metr...

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

Files:

  • packages/core/src/v3/schemas/build.ts
🔇 Additional comments (2)
packages/core/src/v3/schemas/build.ts (2)

10-10: LGTM!


119-119: LGTM!


Walkthrough

The change adds task-level inline and named concurrency limits with per-key and total caps. It replaces queue gate tuple configuration with explicit concurrency and trigger options. It persists and validates queue gates across task metadata, runs, queues, and replay flows. It adds concurrency metadata to worker manifests and resource catalogs. It also renames queue concurrency ceilings to combinedConcurrencyLimit and adds integration and parser tests.

Merge Risk: 🟡 Moderate · up to 49947

This change exposes queue gates and concurrency limits across task triggering and worker manifests, but unresolved SDK and metadata behaviors can cause requested concurrency controls to be ignored, truncated, or interpreted differently than configured. Resolve these issues before merge.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 39.29% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 28 functions across 31 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description gives a detailed and relevant summary of the concurrency design, API behavior, and runtime integration. It does not follow most template sections: it omits the issue reference, checkli… Add the required template sections. Include the issue reference, completed checklist, exact testing steps and results, a concise changelog entry, and screenshots or an explicit statement that screenshots are not applicable.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title describes the queue-gate portion of the changes, but the main public change is task-level concurrency support with named limits and trigger-time overrides. It remains related to the changese…
Full details: Description check

Explanation

The description gives a detailed and relevant summary of the concurrency design, API behavior, and runtime integration. It does not follow most template sections: it omits the issue reference, checklist, testing steps, changelog section, and screenshots section.

✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/queue-gates-contract

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

devin-ai-integration[bot]

This comment was marked as resolved.

@matt-aitken
matt-aitken force-pushed the feat/queue-gates-contract branch from 03c5b34 to d0d9e38 Compare August 29, 2026 09:42
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@pkg-pr-new

pkg-pr-new Bot commented Aug 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

@trigger.dev/build

npm i https://pkg.pr.new/@trigger.dev/build@9807331

trigger.dev

npm i https://pkg.pr.new/trigger.dev@9807331

@trigger.dev/core

npm i https://pkg.pr.new/@trigger.dev/core@9807331

@trigger.dev/python

npm i https://pkg.pr.new/@trigger.dev/python@9807331

@trigger.dev/react-hooks

npm i https://pkg.pr.new/@trigger.dev/react-hooks@9807331

@trigger.dev/redis-worker

npm i https://pkg.pr.new/@trigger.dev/redis-worker@9807331

@trigger.dev/rsc

npm i https://pkg.pr.new/@trigger.dev/rsc@9807331

@trigger.dev/schema-to-json

npm i https://pkg.pr.new/@trigger.dev/schema-to-json@9807331

@trigger.dev/sdk

npm i https://pkg.pr.new/@trigger.dev/sdk@9807331

commit: 9807331

@matt-aitken
matt-aitken force-pushed the feat/queue-gates-contract branch from 1300356 to 92dbd83 Compare August 29, 2026 18:11
@matt-aitken
matt-aitken force-pushed the feat/queue-gates-contract branch from 92dbd83 to 4e2b5a7 Compare August 29, 2026 18:18
@matt-aitken
matt-aitken force-pushed the feat/queue-gates-contract branch from 4e2b5a7 to 9af8f2e Compare August 29, 2026 18:21
@matt-aitken
matt-aitken force-pushed the feat/queue-gates-contract branch from 9af8f2e to d604cb3 Compare August 31, 2026 12:02
@matt-aitken
matt-aitken force-pushed the feat/queue-gates-contract branch from 8f2cf5f to 11d93a6 Compare August 31, 2026 12:37
@matt-aitken
matt-aitken force-pushed the feat/queue-gates-contract branch 2 times, most recently from 6b562dc to 9e6929d Compare August 31, 2026 15:53
@matt-aitken
matt-aitken force-pushed the feat/queue-gates-contract branch from 9e6929d to 72773fe Compare September 6, 2026 12:20
coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

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.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 0 new potential issues.

Devin Review

@matt-aitken
matt-aitken force-pushed the feat/queue-gates-contract branch from f5e0b0f to ad1fb33 Compare September 6, 2026 13:23

@devin-ai-integration devin-ai-integration Bot left a comment

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.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 0 new potential issues.

Devin Review

…mpty gate names

The dedicated run-ops TaskRun schema gains the same nullable gates column
and migration so run creation keeps working with run-operations splitting
enabled (the schema parity test covers it). Gate names and keys are also
required to be non-empty everywhere they enter, so a configured gate can
never be silently dropped by sanitization.
The queue trigger option is a name or a [name, ...gates] tuple; the hook now
splits it into the home queue and the gate list the request body expects
instead of assuming a plain string.
A queue tuple's gate list replaces the task's gates for that run, so [name]
must send an empty list rather than omitting the field and inheriting them.
Matches the SDK's normalization.
…ncurrencyLimit

The public option now says what it bounds: the combined concurrency of all
the queue's concurrencyKey values. Engine internals and storage keep their
existing names.
Two definitions of the same queue that differ only in combinedConcurrencyLimit
now warn and keep the first definition instead of letting registration order
pick the deployed cap.
A bare string home (alone or as a tuple's first element) no longer registers
queue metadata, so module evaluation order can no longer shadow a declared
queue's limits with an empty definition.
Both gate parsers now drop gates with empty or over-length queue names,
drop gates whose literal key exceeds the manifest cap, and treat an
empty-string key as omitted so it inherits the run's key instead of
pinning an empty pool.
The webapp and the enqueue system carried identical copies of the gate
contract with nothing enforcing their parity. parseGates now lives in
the run-engine package with unit tests covering every rule (bounds kept
inclusive at 128, empty keys inherit, malformed entries dropped, capped
at two), and both callers delegate to it.
Tasks declare concurrency directly: an inline { perKey, total } shape
caps the task, and concurrencyLimit() declares a named, shareable limit
that tasks hold via the same option (at most one inline plus two named).
Trigger calls switch a run's named limits with their own concurrency
option, strings only like queue. The queue tuple syntax and
combinedConcurrencyLimit never ship: queue() is a line again, its
concurrencyLimit deprecated in place, and the manifest carries the new
declarations for the server to compile.
Removes the combined override client methods the previous commit
intended to drop, moves react-hooks trigger options off the queue tuple
onto the concurrency names, merges a duplicated type import, and marks
the new type-only imports as such for the linter.
Passing undefined for a gateless original fell back to the task's
current gates, so old runs replayed after new limits were added would
silently gain them.
The index workers emit them into WorkerManifest, which is a separate
schema from the deploy metadata and also needs the field.
The option was silently dropped by the seven single-task and
subscribe/wait paths and doubled at four by-id batch sites. A named
limit object written inline in a task's concurrency now registers as a
declaration instead of losing its caps, and trigger-time limit names
must be non-empty strings.
Names are 1-122 characters of letters, numbers, underscores and
hyphens, checked by the factory, task declarations and trigger
options alike, so distinct names always stay distinct after queue
name sanitization.
…d limits

The end-to-end gate capacity is three (the anonymous inline-limit gate
plus two named limits), so trigger-time replacement can no longer lose
a named limit to a downstream cap. Limit shapes also start at 1: block
everything is a pause or an override, not a declared limit, so a stored
zero always reads as no limit.
Gate capacity is four end to end (three requested gates plus the
task's anonymous inline-limit gate), so replaying a three-gate run
against a task that later gained an inline limit resolves to four and
still enqueues. A run stored without gates now replays with the task's
currently declared limits instead of a fabricated empty array that
silently cleared them, matching how replays adopt the current queue
and retry config.
@matt-aitken
matt-aitken force-pushed the feat/queue-gates-contract branch from c2ebb50 to ff350ef Compare September 7, 2026 00:25

@devin-ai-integration devin-ai-integration Bot left a comment

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.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 0 new potential issues.

Devin Review

Manage named concurrency limits at runtime: list and retrieve report
each limit's bounds plus its live running and queued counts, override
changes only the given bounds (total zero pauses the limit), and reset
restores the declared values.
devin-ai-integration[bot]

This comment was marked as resolved.

…ting

Matches the SDK's trigger validation, and the shared changeset now
versions react-hooks alongside sdk and core.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant