From 26d81959bdb95e80dde0c1692b032bd37d0ef970 Mon Sep 17 00:00:00 2001 From: myftija Date: Mon, 22 Sep 2025 10:37:39 +0200 Subject: [PATCH] feat(api): defer remote build creation for pending deployments Depot builds have short-lived tokens and their TTL is not exposed in the SDK. As queued deployments can stay in the queue for an arbitrary amount of time, deferring the remote build creation helps avoid expired Depot token issues. --- .../api.v1.deployments.$deploymentId.start.ts | 2 ++ .../app/v3/remoteImageBuilder.server.ts | 7 +++-- .../app/v3/services/deployment.server.ts | 31 ++++++++++++++++--- .../services/initializeDeployment.server.ts | 8 +++-- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/apps/webapp/app/routes/api.v1.deployments.$deploymentId.start.ts b/apps/webapp/app/routes/api.v1.deployments.$deploymentId.start.ts index da73cf1be3..1272a8418d 100644 --- a/apps/webapp/app/routes/api.v1.deployments.$deploymentId.start.ts +++ b/apps/webapp/app/routes/api.v1.deployments.$deploymentId.start.ts @@ -61,6 +61,8 @@ export async function action({ request, params }: ActionFunctionArgs) { return json({ error: "Deployment not found" }, { status: 404 }); case "deployment_not_pending": return json({ error: "Deployment is not pending" }, { status: 409 }); + case "failed_to_create_remote_build": + return json({ error: "Failed to create remote build" }, { status: 500 }); case "other": default: error.type satisfies "other"; diff --git a/apps/webapp/app/v3/remoteImageBuilder.server.ts b/apps/webapp/app/v3/remoteImageBuilder.server.ts index e1e6916c41..dca7fffb11 100644 --- a/apps/webapp/app/v3/remoteImageBuilder.server.ts +++ b/apps/webapp/app/v3/remoteImageBuilder.server.ts @@ -1,9 +1,12 @@ import { depot } from "@depot/sdk-node"; -import { Project } from "@trigger.dev/database"; +import { type ExternalBuildData } from "@trigger.dev/core/v3"; +import { type Project } from "@trigger.dev/database"; import { prisma } from "~/db.server"; import { env } from "~/env.server"; -export async function createRemoteImageBuild(project: Project) { +export async function createRemoteImageBuild( + project: Project +): Promise { if (!remoteBuildsEnabled()) { return; } diff --git a/apps/webapp/app/v3/services/deployment.server.ts b/apps/webapp/app/v3/services/deployment.server.ts index 9789cfc07e..495ea91496 100644 --- a/apps/webapp/app/v3/services/deployment.server.ts +++ b/apps/webapp/app/v3/services/deployment.server.ts @@ -2,9 +2,10 @@ import { type AuthenticatedEnvironment } from "~/services/apiAuth.server"; import { BaseService } from "./baseService.server"; import { errAsync, fromPromise, okAsync } from "neverthrow"; import { type WorkerDeploymentStatus, type WorkerDeployment } from "@trigger.dev/database"; -import { logger, type GitMeta } from "@trigger.dev/core/v3"; +import { type ExternalBuildData, logger, type GitMeta } from "@trigger.dev/core/v3"; import { TimeoutDeploymentService } from "./timeoutDeployment.server"; import { env } from "~/env.server"; +import { createRemoteImageBuild } from "../remoteImageBuilder.server"; export class DeploymentService extends BaseService { public startDeployment( @@ -46,11 +47,29 @@ export class DeploymentService extends BaseService { return okAsync(deployment); }; - const updateDeployment = (deployment: Pick) => + const createRemoteBuild = (deployment: Pick) => + fromPromise(createRemoteImageBuild(authenticatedEnv.project), (error) => ({ + type: "failed_to_create_remote_build" as const, + cause: error, + })).map((build) => ({ + id: deployment.id, + externalBuildData: build, + })); + + const updateDeployment = ( + deployment: Pick & { + externalBuildData: ExternalBuildData | undefined; + } + ) => fromPromise( this._prisma.workerDeployment.updateMany({ where: { id: deployment.id, status: "PENDING" }, // status could've changed in the meantime, we're not locking the row - data: { ...updates, status: "BUILDING", startedAt: new Date() }, + data: { + ...updates, + externalBuildData: deployment.externalBuildData, + status: "BUILDING", + startedAt: new Date(), + }, }), (error) => ({ type: "other" as const, @@ -75,11 +94,13 @@ export class DeploymentService extends BaseService { type: "failed_to_extend_deployment_timeout" as const, cause: error, }) - ).map(() => undefined); + ); return getDeployment() .andThen(validateDeployment) + .andThen(createRemoteBuild) .andThen(updateDeployment) - .andThen(extendTimeout); + .andThen(extendTimeout) + .map(() => undefined); } } diff --git a/apps/webapp/app/v3/services/initializeDeployment.server.ts b/apps/webapp/app/v3/services/initializeDeployment.server.ts index a355522772..0758dc231e 100644 --- a/apps/webapp/app/v3/services/initializeDeployment.server.ts +++ b/apps/webapp/app/v3/services/initializeDeployment.server.ts @@ -87,8 +87,12 @@ export class InitializeDeploymentService extends BaseService { ); } - // Try and create a depot build and get back the external build data - const externalBuildData = await createRemoteImageBuild(environment.project); + // 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" + ? undefined + : await createRemoteImageBuild(environment.project); const triggeredBy = payload.userId ? await this._prisma.user.findFirst({