From d36ccae7c30b7753f5d4b70c5c9942c3e6b5e2f1 Mon Sep 17 00:00:00 2001 From: Saadi Myftija Date: Fri, 4 Sep 2026 11:19:14 +0200 Subject: [PATCH 1/6] fix(cli): report build log stream disconnects clearly and exit instead of hanging When the log stream for a build server deploy fails mid-build, the CLI printed the raw stream error and then kept running until some handle timed out about an hour later. It now stops the spinner, says the stream was lost and that the deployment itself is still running, and exits with code 1 right away, since it cannot know the final outcome. --- .changeset/deploy-log-stream-disconnect.md | 5 ++++ packages/cli-v3/src/commands/deploy.ts | 27 ++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 .changeset/deploy-log-stream-disconnect.md diff --git a/.changeset/deploy-log-stream-disconnect.md b/.changeset/deploy-log-stream-disconnect.md new file mode 100644 index 00000000000..6ee147e06ce --- /dev/null +++ b/.changeset/deploy-log-stream-disconnect.md @@ -0,0 +1,5 @@ +--- +"trigger.dev": patch +--- + +When the build log stream disconnects during a build server deploy, the CLI now explains that the deployment itself is still running and exits immediately with a non-zero code, instead of printing the raw stream error and hanging. diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 0868c05dfcf..7bdfebd0f56 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -2331,10 +2331,33 @@ async function followBuildServerDeployment({ return process.exit(0); } - const finalDeploymentEvent = await streamDeploymentEvents(readSession, renderer, () => - abortController.abort() + const [streamError, finalDeploymentEvent] = await tryCatch( + streamDeploymentEvents(readSession, renderer, () => abortController.abort()).finally(() => + abortController.abort() + ) ); + if (streamError) { + renderer.finish("Log stream stopped", "failure"); + + logger.debug("Build log stream failed", { error: streamError }); + + log.error(`Lost connection to the build log stream: ${streamError.message}`); + log.info( + "This is not a deployment failure, the build server is still working on it. Check the dashboard for the final status." + ); + + if (!isLinksSupported) { + log.info(`View deployment: ${rawDeploymentLink}`); + } + + throw new OutroCommandError( + `Version ${deployment.version} ${ + isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" + }` + ); + } + if (!renderer.started && !finalDeploymentEvent) { // unlikely that it happens in practice, only in rare corner cases // the timeout would kick in earlier if the build server fails to dequeue the build From 222e8a649e4d765f0d2f6326a1c310b2c348aeff Mon Sep 17 00:00:00 2001 From: Saadi Myftija Date: Fri, 4 Sep 2026 11:27:40 +0200 Subject: [PATCH 2/6] fix(cli): stop reading the build log stream once the finalized event arrives Returning from the event loop cancels the underlying read session, so a late transport error cannot discard a finalized result that was already received, and the stream cannot keep reconnecting after the build is done. --- packages/cli-v3/src/commands/deploy.ts | 4 +-- packages/cli-v3/src/deploy/buildLogs.test.ts | 28 ++++++++++++++++++++ packages/cli-v3/src/deploy/buildLogs.ts | 7 ++--- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 7bdfebd0f56..2cbd86c4e36 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -2332,9 +2332,7 @@ async function followBuildServerDeployment({ } const [streamError, finalDeploymentEvent] = await tryCatch( - streamDeploymentEvents(readSession, renderer, () => abortController.abort()).finally(() => - abortController.abort() - ) + streamDeploymentEvents(readSession, renderer, () => abortController.abort()) ); if (streamError) { diff --git a/packages/cli-v3/src/deploy/buildLogs.test.ts b/packages/cli-v3/src/deploy/buildLogs.test.ts index 62efac5dd51..2022d1e088c 100644 --- a/packages/cli-v3/src/deploy/buildLogs.test.ts +++ b/packages/cli-v3/src/deploy/buildLogs.test.ts @@ -258,6 +258,34 @@ describe("streamDeploymentEvents", () => { expect(onFinalized).toHaveBeenCalledTimes(1); }); + it("stops consuming the stream once the finalized event arrives", async () => { + let released = false; + async function* recordsThenThrow() { + try { + yield { + seqNum: 0, + timestamp: 1_700_000_000_000, + body: JSON.stringify({ type: "log", data: { message: "a" } }), + }; + yield { + seqNum: 1, + timestamp: 1_700_000_000_000, + body: JSON.stringify({ type: "finalized", data: { result: "succeeded" } }), + }; + throw new Error("stream closed with unparsed data remaining"); + } finally { + released = true; + } + } + const final = await streamDeploymentEvents( + recordsThenThrow(), + { started: false, log: vi.fn(), finish: vi.fn() }, + vi.fn() + ); + expect(final).toEqual({ result: "succeeded" }); + expect(released).toBe(true); + }); + it("returns undefined when the stream ends without a finalized event", async () => { const final = await streamDeploymentEvents( records([JSON.stringify({ type: "log", data: { message: "a" } })]), diff --git a/packages/cli-v3/src/deploy/buildLogs.ts b/packages/cli-v3/src/deploy/buildLogs.ts index 8241ca7a5d9..21b04fa42bd 100644 --- a/packages/cli-v3/src/deploy/buildLogs.ts +++ b/packages/cli-v3/src/deploy/buildLogs.ts @@ -158,8 +158,6 @@ export async function streamDeploymentEvents( renderer: BuildLogRenderer, onFinalized: () => void ): Promise { - let finalEvent: DeploymentFinalizedEvent["data"] | undefined; - for await (const record of records) { const result = DeploymentEventFromString.safeParse(record.body); if (!result.success) { @@ -182,9 +180,8 @@ export async function streamDeploymentEvents( break; } case "finalized": { - finalEvent = event.data; onFinalized(); - break; + return event.data; } default: { event satisfies never; @@ -193,5 +190,5 @@ export async function streamDeploymentEvents( } } - return finalEvent; + return undefined; } From 123f091e7330f17328b9d9af1f108d18eecf15a0 Mon Sep 17 00:00:00 2001 From: Saadi Myftija Date: Fri, 4 Sep 2026 11:37:30 +0200 Subject: [PATCH 3/6] fix(cli): neutral wording and bounded cause for build log stream failures --- packages/cli-v3/src/commands/deploy.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 2cbd86c4e36..e3905b6c9de 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -2340,9 +2340,13 @@ async function followBuildServerDeployment({ logger.debug("Build log stream failed", { error: streamError }); - log.error(`Lost connection to the build log stream: ${streamError.message}`); + const reason = (streamError instanceof Error ? streamError.message : String(streamError)) + .replace(/\s+/g, " ") + .slice(0, 200); + + log.error(`Build log stream failed: ${reason}`); log.info( - "This is not a deployment failure, the build server is still working on it. Check the dashboard for the final status." + "The deployment itself is unaffected and continues on the build server. Check the dashboard for the final status." ); if (!isLinksSupported) { From 67e4c2dda635400b91d0fa68adba3933e67a3804 Mon Sep 17 00:00:00 2001 From: Saadi Myftija Date: Fri, 4 Sep 2026 12:17:54 +0200 Subject: [PATCH 4/6] fix(cli): exit non-zero when the build log stream cannot be opened Failing to open the stream and losing it mid-build leave the CLI in the same position, unable to confirm the deployment outcome, so both now use the same message and exit code. --- .changeset/deploy-log-stream-disconnect.md | 2 +- packages/cli-v3/src/commands/deploy.ts | 65 +++++++++++----------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/.changeset/deploy-log-stream-disconnect.md b/.changeset/deploy-log-stream-disconnect.md index 6ee147e06ce..f9db45375e6 100644 --- a/.changeset/deploy-log-stream-disconnect.md +++ b/.changeset/deploy-log-stream-disconnect.md @@ -2,4 +2,4 @@ "trigger.dev": patch --- -When the build log stream disconnects during a build server deploy, the CLI now explains that the deployment itself is still running and exits immediately with a non-zero code, instead of printing the raw stream error and hanging. +When the build log stream cannot be opened or disconnects during a build server deploy, the CLI now explains that the deployment itself is unaffected and exits immediately with a non-zero code, since it can no longer confirm the outcome. Previously a disconnect printed the raw stream error and left the process hanging. diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index e3905b6c9de..26d493346d7 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -26,6 +26,7 @@ import { resolveAlwaysExternal } from "../build/externals.js"; import { createContextArchive, getArchiveSize } from "../deploy/archiveContext.js"; import { createBundleArchive } from "../deploy/bundleArchive.js"; import { + BuildLogRenderer, BuildLogsMode, createBuildLogRenderer, resolveBuildLogsMode, @@ -2284,6 +2285,36 @@ function showFullBuildLogs(options: DeployCommandOptions) { return resolveBuildLogsMode(options.buildLogs, buildLogsEnv(options)) === "full"; } +function buildLogStreamError( + error: unknown, + renderer: BuildLogRenderer, + deployment: Pick, + rawDeploymentLink: string +): OutroCommandError { + renderer.finish("Log stream stopped", "failure"); + + logger.debug("Build log stream failed", { error }); + + const reason = (error instanceof Error ? error.message : String(error)) + .replace(/\s+/g, " ") + .slice(0, 200); + + log.error(`Build log stream failed: ${reason}`); + log.info( + "The deployment itself is unaffected and continues on the build server. Check the dashboard for the final status." + ); + + if (!isLinksSupported) { + log.info(`View deployment: ${rawDeploymentLink}`); + } + + return new OutroCommandError( + `Version ${deployment.version} ${ + isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" + }` + ); +} + async function followBuildServerDeployment({ deployment, eventStream, @@ -2319,16 +2350,7 @@ async function followBuildServerDeployment({ ); if (readSessionError) { - renderer.finish("Failed to query build progress", "abandoned"); - log.warn(`Failed streaming build logs, open the deployment in the dashboard to view the logs`); - - outro( - `Version ${deployment.version} is being deployed ${ - isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" - }` - ); - - return process.exit(0); + throw buildLogStreamError(readSessionError, renderer, deployment, rawDeploymentLink); } const [streamError, finalDeploymentEvent] = await tryCatch( @@ -2336,28 +2358,7 @@ async function followBuildServerDeployment({ ); if (streamError) { - renderer.finish("Log stream stopped", "failure"); - - logger.debug("Build log stream failed", { error: streamError }); - - const reason = (streamError instanceof Error ? streamError.message : String(streamError)) - .replace(/\s+/g, " ") - .slice(0, 200); - - log.error(`Build log stream failed: ${reason}`); - log.info( - "The deployment itself is unaffected and continues on the build server. Check the dashboard for the final status." - ); - - if (!isLinksSupported) { - log.info(`View deployment: ${rawDeploymentLink}`); - } - - throw new OutroCommandError( - `Version ${deployment.version} ${ - isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" - }` - ); + throw buildLogStreamError(streamError, renderer, deployment, rawDeploymentLink); } if (!renderer.started && !finalDeploymentEvent) { From 0fe474b314491d5392411506b1c325b98104b046 Mon Sep 17 00:00:00 2001 From: Saadi Myftija Date: Fri, 4 Sep 2026 12:27:38 +0200 Subject: [PATCH 5/6] test(cli): use plain callbacks in the finalized-stream test --- packages/cli-v3/src/deploy/buildLogs.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/cli-v3/src/deploy/buildLogs.test.ts b/packages/cli-v3/src/deploy/buildLogs.test.ts index 2022d1e088c..565080cb0f2 100644 --- a/packages/cli-v3/src/deploy/buildLogs.test.ts +++ b/packages/cli-v3/src/deploy/buildLogs.test.ts @@ -277,12 +277,16 @@ describe("streamDeploymentEvents", () => { released = true; } } + let finalizedCalls = 0; const final = await streamDeploymentEvents( recordsThenThrow(), - { started: false, log: vi.fn(), finish: vi.fn() }, - vi.fn() + { started: false, log: () => {}, finish: () => {} }, + () => { + finalizedCalls++; + } ); expect(final).toEqual({ result: "succeeded" }); + expect(finalizedCalls).toBe(1); expect(released).toBe(true); }); From 2a0691b194903294aa5b28534c744ffc4147fc98 Mon Sep 17 00:00:00 2001 From: Saadi Myftija Date: Fri, 4 Sep 2026 12:39:51 +0200 Subject: [PATCH 6/6] fix(cli): import BuildLogRenderer as a type --- packages/cli-v3/src/commands/deploy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 26d493346d7..6894b85afa2 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -26,7 +26,7 @@ import { resolveAlwaysExternal } from "../build/externals.js"; import { createContextArchive, getArchiveSize } from "../deploy/archiveContext.js"; import { createBundleArchive } from "../deploy/bundleArchive.js"; import { - BuildLogRenderer, + type BuildLogRenderer, BuildLogsMode, createBuildLogRenderer, resolveBuildLogsMode,