You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 8f7617a
Browse filesBrowse the repository at this point in the historyBrowse files
feat(run-engine): trigger tasks pinned to an external deployment id
The SDK discovers an external deployment id at runtime (explicit TRIGGER_EXTERNAL_DEPLOYMENT_ID always; platform commit-SHA variables and generic fallbacks when TRIGGER_AUTOMATIC_SKEW_VERSION_PROTECTION=1) and sends it alongside lockToVersion; the server resolves precedence (version > external id > current). An id held by a deployed deployment pins the run to that worker; an in-flight or unknown id parks the run in PENDING_VERSION with the id in TaskRun.annotations, wakes it pinned when a deployment carrying the id finalizes (ClickHouse candidates, Postgres authoritative), and expires it after a deadline that re-checks Postgres before acting. Parking outranks delaying and preserves delayUntil. The id is projected to ClickHouse task_runs_v2.external_deployment_id during replication. Redis cache for id-to-worker resolution, guarded version-aware writes.
Ids are not unique. Several deployments can hold one id - a --force rebuild is the ordinary way to get there - so resolution always picks the highest version among the candidates, never the newest by timestamp. The rule is applied identically on both paths that can bind a run to a worker: resolveExternalDeployment at trigger time, and PendingVersionSystem when a landing deployment wakes a parked run. Version comparison is numeric on the counter half, so 20260807.10 outranks 20260807.9.
A run whose id never lands expires at the deadline with EXTERNAL_DEPLOYMENT_NOT_FOUND and an error naming the id it waited for, which is what a failed build or a typo looks like from the caller. Default deadline is one hour (EXTERNAL_DEPLOYMENT_PARK_DEADLINE_MS).
Debounce registration happens in both the parked and the delayed branch through one helper, so a debounced run that parks still binds its debounce key; without it every later trigger for the same key created another parked run, and all of them executed when the deployment landed. The two DELAYED-only status checks in DebounceSystem also accept PENDING_VERSION, without which the lock-contention fallback would rethrow a 5xx the SDK retries and amplifies, and the fast path would push every trigger on a parked key through the redlock.
Resolution is skipped in development. A dev environment cannot hold a WorkerDeployment - trigger dev registers a BackgroundWorker with nothing behind it, and deploy --env refuses dev - so an external deployment id there could only ever park, and the parked run then expired against the dev TTL while a connected dev worker sat idle. The id is still annotated so the dashboard shows what the app sent (TRI-13000).
Runs can now be pinned to the deployment that your calling code came from, so a request served by an old release never triggers tasks from a new one.
7
+
8
+
Deploy with an id — a commit SHA, a CI run id, a release tag — and give the running app the same value:
9
+
10
+
```bash
11
+
trigger.dev deploy --external-id "$COMMIT_SHA"
12
+
TRIGGER_EXTERNAL_DEPLOYMENT_ID="$COMMIT_SHA"
13
+
```
14
+
15
+
Every trigger from that app then runs on that deployment's tasks. On Vercel, Railway, Render, Cloudflare Pages, Koyeb and most CI systems you can skip the second line and set `TRIGGER_AUTOMATIC_SKEW_VERSION_PROTECTION=1` instead — the SDK finds the commit itself. You can also set it per call:
**While the deployment is still building**, the run waits instead of running on the wrong version. It starts as soon as a deployment carrying that id finishes, pinned to it. This is the normal case when your app deploys before its tasks finish building.
22
+
23
+
**If the deployment never arrives** — the build failed, or nothing was ever deployed under that id — the run waits for an hour and then expires, and the error names the id it was waiting for. It is never quietly sent to a different release. Trigger a run with an id you have not deployed yet and this is what you get, so an id typo shows up as an expired run rather than a run on the wrong code.
24
+
25
+
**If several deployments share one id**, the run goes to the highest version among them. This is what you get after `trigger.dev deploy --external-id "$COMMIT_SHA" --force`, which builds a second deployment for an id that already has one: both are kept and new runs use the newer.
26
+
27
+
Triggering repeatedly with the same id is stable — every run lands on the same deployment until a newer one claims that id.
28
+
29
+
An explicit `version` (or `TRIGGER_VERSION`) still takes precedence over the id, and triggers that send no id behave exactly as before.
0 commit comments