From 9f19f50ea02fa5f493d11ccc81b56847e0324429 Mon Sep 17 00:00:00 2001 From: Saadi Myftija Date: Mon, 16 Feb 2026 17:40:30 +0100 Subject: [PATCH 1/5] Fix external build token generation issue --- .../app/v3/services/deployment.server.ts | 28 ++++++++++--------- .../services/initializeDeployment.server.ts | 18 ++++++++---- packages/cli-v3/src/commands/deploy.ts | 19 +++++++++---- packages/core/src/v3/schemas/api.ts | 3 +- 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/apps/webapp/app/v3/services/deployment.server.ts b/apps/webapp/app/v3/services/deployment.server.ts index 56888284ba1..985e0e47543 100644 --- a/apps/webapp/app/v3/services/deployment.server.ts +++ b/apps/webapp/app/v3/services/deployment.server.ts @@ -110,9 +110,9 @@ export class DeploymentService extends BaseService { const createRemoteBuildIfNeeded = deployment.buildServerMetadata?.isNativeBuild ? okAsync(undefined) : fromPromise(createRemoteImageBuild(authenticatedEnv.project), (error) => ({ - type: "failed_to_create_remote_build" as const, - cause: error, - })); + type: "failed_to_create_remote_build" as const, + cause: error, + })); const existingBuildServerMetadata = deployment.buildServerMetadata as | BuildServerMetadata @@ -134,7 +134,7 @@ export class DeploymentService extends BaseService { }, } : {}), - externalBuildData, + ...(externalBuildData ? { externalBuildData } : {}), status: "BUILDING", installedAt: new Date(), }, @@ -496,14 +496,16 @@ export class DeploymentService extends BaseService { type: "other" as const, cause: error, }) - ).andThen((deployment) => { - if (!deployment) { - return errAsync({ type: "deployment_not_found" as const }); - } - return okAsync(deployment); - }).map((deployment) => ({ - ...deployment, - buildServerMetadata: BuildServerMetadata.safeParse(deployment.buildServerMetadata).data, - })); + ) + .andThen((deployment) => { + if (!deployment) { + return errAsync({ type: "deployment_not_found" as const }); + } + return okAsync(deployment); + }) + .map((deployment) => ({ + ...deployment, + buildServerMetadata: BuildServerMetadata.safeParse(deployment.buildServerMetadata).data, + })); } } diff --git a/apps/webapp/app/v3/services/initializeDeployment.server.ts b/apps/webapp/app/v3/services/initializeDeployment.server.ts index 939cfd7e6f9..5dacaac7bb5 100644 --- a/apps/webapp/app/v3/services/initializeDeployment.server.ts +++ b/apps/webapp/app/v3/services/initializeDeployment.server.ts @@ -89,12 +89,18 @@ export class InitializeDeploymentService extends BaseService { ); } - // For the `PENDING` initial status, defer the creation of the Depot build until the deployment is started. - // This helps avoid Depot token expiration issues. - const externalBuildData = - payload.initialStatus === "PENDING" || payload.isNativeBuild - ? undefined - : await createRemoteImageBuild(environment.project); + // For the `PENDING` initial status, defer the creation of the Depot build until the deployment is started to avoid token expiration issues. + // 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. + const generateExternalBuildToken = + payload.initialStatus === "PENDING" || payload.isNativeBuild || payload.isLocal; + + const externalBuildData = generateExternalBuildToken + ? ({ + projectId: "-", + buildToken: "-", + buildId: "-", + } satisfies ExternalBuildData) + : await createRemoteImageBuild(environment.project); const triggeredBy = payload.userId ? await this._prisma.user.findFirst({ diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index f4de03281c8..4e14029ec87 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -201,7 +201,11 @@ export function configureDeployCommand(program: Command) { localBuild: true, }) ) - .addOption(new CommandOption("--local-build", "Build the deployment image locally")) + .addOption( + new CommandOption("--local-build", "Build the deployment image locally").conflicts( + "nativeBuildServer" + ) + ) .addOption(new CommandOption("--push", "Push the image after local builds").hideHelp()) .addOption( new CommandOption("--no-push", "Do not push the image after local builds").hideHelp() @@ -420,14 +424,19 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { gitMeta, type: features.run_engine_v2 ? "MANAGED" : "V1", runtime: buildManifest.runtime, + isLocal: options.localBuild, isNativeBuild: false, triggeredVia: getTriggeredVia(), }, envVars.TRIGGER_EXISTING_DEPLOYMENT_ID ); + + // When `externalBuildData` is not present the deployment implicitly goes into the local build path + // which is used in self-hosted setups. There are a few subtle differences between local builds for the cloud + // and local builds for self-hosted setups. We need to make the separation of the two paths clearer to avoid confusion. const isLocalBuild = options.localBuild || !deployment.externalBuildData; - // Would be best to actually store this separately in the deployment object. This is an okay proxy for now. - const remoteBuildExplicitlySkipped = options.localBuild && !!deployment.externalBuildData; + const authenticateToTriggerRegistry = options.localBuild; + const skipServerSideRegistryPush = options.localBuild; // Fail fast if we know local builds will fail if (isLocalBuild) { @@ -559,7 +568,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { network: options.network, builder: options.builder, push: options.push, - authenticateToRegistry: remoteBuildExplicitlySkipped, + authenticateToRegistry: authenticateToTriggerRegistry, }); logger.debug("Build result", buildResult); @@ -657,7 +666,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { { imageDigest: buildResult.digest, skipPromotion: options.skipPromotion, - skipPushToRegistry: remoteBuildExplicitlySkipped, + skipPushToRegistry: skipServerSideRegistryPush, }, (logMessage) => { if (options.plain || isCI) { diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index 76f93af3ffb..d3af673127e 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -602,7 +602,7 @@ const InitializeDeploymentRequestBodyBase = z.object({ runtime: z.string().optional(), initialStatus: z.enum(["PENDING", "BUILDING"]).optional(), triggeredVia: DeploymentTriggeredVia.optional(), - buildId: z.string().optional() + buildId: z.string().optional(), }); type BaseOutput = z.output; @@ -624,6 +624,7 @@ type NonNativeBuildOutput = BaseOutput & { const InitializeDeploymentRequestBodyFull = InitializeDeploymentRequestBodyBase.extend({ isNativeBuild: z.boolean().default(false), + isLocal: z.boolean().optional(), skipPromotion: z.boolean().optional(), artifactKey: z.string().optional(), configFilePath: z.string().optional(), From 58841ef4b052988f612bfbcc2f1c71124da7f679 Mon Sep 17 00:00:00 2001 From: Saadi Myftija Date: Mon, 16 Feb 2026 17:58:21 +0100 Subject: [PATCH 2/5] Add changeset --- .changeset/tricky-suits-design.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/tricky-suits-design.md diff --git a/.changeset/tricky-suits-design.md b/.changeset/tricky-suits-design.md new file mode 100644 index 00000000000..d00603c0757 --- /dev/null +++ b/.changeset/tricky-suits-design.md @@ -0,0 +1,7 @@ +--- +"@trigger.dev/sdk": patch +"trigger.dev": patch +"@trigger.dev/core": patch +--- + +Fixed a minor issue in the deployment command on distinguishing between local builds for the cloud vs local builds for self-hosting setups. From 9573c385327b7c375858a848e8a979b896e251cf Mon Sep 17 00:00:00 2001 From: Saadi Myftija Date: Mon, 16 Feb 2026 18:04:42 +0100 Subject: [PATCH 3/5] Fix missing type after rebase --- apps/webapp/app/v3/services/initializeDeployment.server.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/webapp/app/v3/services/initializeDeployment.server.ts b/apps/webapp/app/v3/services/initializeDeployment.server.ts index 5dacaac7bb5..8af397dc501 100644 --- a/apps/webapp/app/v3/services/initializeDeployment.server.ts +++ b/apps/webapp/app/v3/services/initializeDeployment.server.ts @@ -1,4 +1,8 @@ -import { BuildServerMetadata, type InitializeDeploymentRequestBody } from "@trigger.dev/core/v3"; +import { + BuildServerMetadata, + type InitializeDeploymentRequestBody, + type ExternalBuildData, +} from "@trigger.dev/core/v3"; import { customAlphabet } from "nanoid"; import { env } from "~/env.server"; import { type AuthenticatedEnvironment } from "~/services/apiAuth.server"; From 653920caef88aee86e34abfe626370b1c9004a80 Mon Sep 17 00:00:00 2001 From: Saadi Myftija Date: Mon, 16 Feb 2026 18:28:54 +0100 Subject: [PATCH 4/5] Fix naming inconsistency --- apps/webapp/app/v3/services/initializeDeployment.server.ts | 2 +- packages/cli-v3/src/commands/deploy.ts | 2 +- packages/core/src/v3/schemas/api.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/webapp/app/v3/services/initializeDeployment.server.ts b/apps/webapp/app/v3/services/initializeDeployment.server.ts index 8af397dc501..987925aa709 100644 --- a/apps/webapp/app/v3/services/initializeDeployment.server.ts +++ b/apps/webapp/app/v3/services/initializeDeployment.server.ts @@ -96,7 +96,7 @@ export class InitializeDeploymentService extends BaseService { // For the `PENDING` initial status, defer the creation of the Depot build until the deployment is started to avoid token expiration issues. // 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. const generateExternalBuildToken = - payload.initialStatus === "PENDING" || payload.isNativeBuild || payload.isLocal; + payload.initialStatus === "PENDING" || payload.isNativeBuild || payload.isLocalBuild; const externalBuildData = generateExternalBuildToken ? ({ diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 4e14029ec87..7ab06914240 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -424,7 +424,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { gitMeta, type: features.run_engine_v2 ? "MANAGED" : "V1", runtime: buildManifest.runtime, - isLocal: options.localBuild, + isLocalBuild: options.localBuild, isNativeBuild: false, triggeredVia: getTriggeredVia(), }, diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index d3af673127e..2a7bcb96502 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -601,6 +601,7 @@ const InitializeDeploymentRequestBodyBase = z.object({ type: z.enum(["MANAGED", "UNMANAGED", "V1"]).optional(), runtime: z.string().optional(), initialStatus: z.enum(["PENDING", "BUILDING"]).optional(), + isLocalBuild: z.boolean().optional(), triggeredVia: DeploymentTriggeredVia.optional(), buildId: z.string().optional(), }); @@ -624,7 +625,6 @@ type NonNativeBuildOutput = BaseOutput & { const InitializeDeploymentRequestBodyFull = InitializeDeploymentRequestBodyBase.extend({ isNativeBuild: z.boolean().default(false), - isLocal: z.boolean().optional(), skipPromotion: z.boolean().optional(), artifactKey: z.string().optional(), configFilePath: z.string().optional(), From b44bfb835e424beb854899fcc5105cfeff9e2338 Mon Sep 17 00:00:00 2001 From: Saadi Myftija Date: Mon, 16 Feb 2026 19:03:43 +0100 Subject: [PATCH 5/5] Add `conflicts` annotation to the deprecated --force-local-build flag too --- packages/cli-v3/src/commands/deploy.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 7ab06914240..7c71b45667b 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -197,9 +197,12 @@ export function configureDeployCommand(program: Command) { ) // Local build options .addOption( - new CommandOption("--force-local-build", "Deprecated alias for --local-build").implies({ - localBuild: true, - }) + new CommandOption("--force-local-build", "Deprecated alias for --local-build") + .implies({ + localBuild: true, + }) + .conflicts("nativeBuildServer") + .hideHelp() ) .addOption( new CommandOption("--local-build", "Build the deployment image locally").conflicts(