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 18ec657
Browse filesBrowse the repository at this point in the historyBrowse files
feat(deploy): --external-id and --force for deploy idempotency
A deploy can carry an opaque external id (commit SHA, CI run id, release tag). Repeating an id that already deployed returns the existing version as a no-op instead of rebuilding; an id with a build in flight is rejected with 409 naming that version; a failed id rebuilds freely. --force is non-destructive to deployments that already succeeded - both persist and the higher version wins - but cancels a build still in flight, so one id never has two live builds racing to define it. Cancelling writes a terminal status and appends a finalized event, which aborts a build the platform drives; a build it does not drive keeps running but can never land, and the CLI says so. Ids are deliberately not unique - reuse is resolved in application code by highest version, never timestamps. The no-op path mints no build credentials and no event stream (TRI-12923).
What that means for callers: a --force rebuild leaves two deployments holding one id, and runs triggered with it go to the higher version once the rebuild lands, so the takeover needs no separate promotion. Until a successful build exists for an id, runs triggered with it park and then expire rather than falling back to current - a failed build is therefore visible to the caller as expired runs, not as runs on the wrong release.
`trigger.dev deploy` can now carry an id of your own choosing, such as a commit SHA, a CI run id or a release tag:
6
+
7
+
```bash
8
+
trigger.dev deploy --external-id $GITHUB_SHA
9
+
```
10
+
11
+
Give the same value to your running app (see `TRIGGER_EXTERNAL_DEPLOYMENT_ID`) and every task it triggers runs on that deployment, so a request served by an old release never triggers tasks from a new one.
12
+
13
+
**Deploying an id that has already been deployed doesn't build an image**: the command reports the existing version, sets the same GitHub Actions outputs a normal deploy would, and exits successfully without pushing anything. A pipeline that re-runs for a commit it already deployed gets that version back instead of a second identical deployment.
14
+
15
+
**If a build for that id is still running**, the deploy is rejected and tells you which version is already in flight, so two builds never race to define one id.
16
+
17
+
**Ids that only ever failed, timed out or were cancelled always build again**, so retrying a broken build just works. Note that a failed build leaves the id undeployed: runs triggered with it wait, and expire after an hour if you never land a successful build for it.
18
+
19
+
The id is at most 128 characters, which fits a SHA-256 commit hash with room to spare.
20
+
21
+
Pass `--force` alongside `--external-id` to build again even when that id is already deployed. Deployments that already succeeded are left alone and both are kept — new runs triggered with that id go to the highest version, so the rebuild takes over once it lands. A build that is still in flight is cancelled instead, so one id never has two live builds racing to define it. `--force` on its own is rejected, since there is nothing for it to act on.
22
+
23
+
Deploys that pass no `--external-id` are unaffected.
@@ -53,7 +96,10 @@ export class InitializeDeploymentService extends BaseService {
53
96
);
54
97
}
55
98
99
+
span.setAttribute("outcome","created");
100
+
56
101
return{
102
+
outcome: "created",
57
103
deployment: existingDeployment,
58
104
imageRef: existingDeployment.imageReference??"",
59
105
};
@@ -103,6 +149,55 @@ export class InitializeDeploymentService extends BaseService {
103
149
);
104
150
}
105
151
152
+
constdeploymentService=newDeploymentService();
153
+
154
+
constreuse=awaitresolveExternalIdReuse({
155
+
prisma: this._prisma,
156
+
environmentId: environment.id,
157
+
externalId: payload.externalId,
158
+
force: payload.force,
159
+
});
160
+
161
+
if(reuse.action==="reject"){
162
+
span.setAttribute("outcome","rejected");
163
+
164
+
thrownewServiceValidationError(
165
+
`A deployment for external id "${payload.externalId}" is already in progress (version ${reuse.deployment.version}). Wait for it to finish, or deploy again with --force to cancel it and start a new one.`,
// For the `PENDING` initial status, defer the creation of the Depot build until the deployment is started to avoid token expiration issues.
107
202
// For local and native builds we don't need to generate the Depot tokens. We still need to create an empty object sadly due to a bug in older CLI versions.
108
203
constgenerateExternalBuildToken=
@@ -140,7 +235,6 @@ export class InitializeDeploymentService extends BaseService {
0 commit comments