Skip to content

Commit b9c8498

Browse files
authored
fix(cli): report build log stream failures clearly and exit instead of hanging (#4887)
When the build log stream failed during a build server deploy, the CLI printed the raw stream error (e.g. `X Error: Invalid access token`) and then hung the process. Only the log stream was lost, the deployment itself kept running. The CLI now stops the spinner, says the log stream failed and that the deployment continues on the build server, links the dashboard, and exits 1 right away. The non-zero exit is deliberate: the CLI can no longer confirm the outcome. Failing to open the stream now behaves the same way instead of exiting 0. ### Fix - One handler for both stream failures that prints the message and throws an `OutroCommandError`, which exits the process instead of waiting for open handles. - Return from the event loop on the `finalized` event, which cancels the read session directly. A late transport error can no longer discard a result already received or keep the stream reconnecting after the build is done.
1 parent f347f0e commit b9c8498

4 files changed

Lines changed: 77 additions & 17 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trigger.dev": patch
3+
---
4+
5+
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.

packages/cli-v3/src/commands/deploy.ts

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { resolveAlwaysExternal } from "../build/externals.js";
2626
import { createContextArchive, getArchiveSize } from "../deploy/archiveContext.js";
2727
import { createBundleArchive } from "../deploy/bundleArchive.js";
2828
import {
29+
type BuildLogRenderer,
2930
BuildLogsMode,
3031
createBuildLogRenderer,
3132
resolveBuildLogsMode,
@@ -2284,6 +2285,36 @@ function showFullBuildLogs(options: DeployCommandOptions) {
22842285
return resolveBuildLogsMode(options.buildLogs, buildLogsEnv(options)) === "full";
22852286
}
22862287

2288+
function buildLogStreamError(
2289+
error: unknown,
2290+
renderer: BuildLogRenderer,
2291+
deployment: Pick<InitializeDeploymentResponseBody, "version">,
2292+
rawDeploymentLink: string
2293+
): OutroCommandError {
2294+
renderer.finish("Log stream stopped", "failure");
2295+
2296+
logger.debug("Build log stream failed", { error });
2297+
2298+
const reason = (error instanceof Error ? error.message : String(error))
2299+
.replace(/\s+/g, " ")
2300+
.slice(0, 200);
2301+
2302+
log.error(`Build log stream failed: ${reason}`);
2303+
log.info(
2304+
"The deployment itself is unaffected and continues on the build server. Check the dashboard for the final status."
2305+
);
2306+
2307+
if (!isLinksSupported) {
2308+
log.info(`View deployment: ${rawDeploymentLink}`);
2309+
}
2310+
2311+
return new OutroCommandError(
2312+
`Version ${deployment.version} ${
2313+
isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : ""
2314+
}`
2315+
);
2316+
}
2317+
22872318
async function followBuildServerDeployment({
22882319
deployment,
22892320
eventStream,
@@ -2319,22 +2350,17 @@ async function followBuildServerDeployment({
23192350
);
23202351

23212352
if (readSessionError) {
2322-
renderer.finish("Failed to query build progress", "abandoned");
2323-
log.warn(`Failed streaming build logs, open the deployment in the dashboard to view the logs`);
2324-
2325-
outro(
2326-
`Version ${deployment.version} is being deployed ${
2327-
isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : ""
2328-
}`
2329-
);
2330-
2331-
return process.exit(0);
2353+
throw buildLogStreamError(readSessionError, renderer, deployment, rawDeploymentLink);
23322354
}
23332355

2334-
const finalDeploymentEvent = await streamDeploymentEvents(readSession, renderer, () =>
2335-
abortController.abort()
2356+
const [streamError, finalDeploymentEvent] = await tryCatch(
2357+
streamDeploymentEvents(readSession, renderer, () => abortController.abort())
23362358
);
23372359

2360+
if (streamError) {
2361+
throw buildLogStreamError(streamError, renderer, deployment, rawDeploymentLink);
2362+
}
2363+
23382364
if (!renderer.started && !finalDeploymentEvent) {
23392365
// unlikely that it happens in practice, only in rare corner cases
23402366
// the timeout would kick in earlier if the build server fails to dequeue the build

packages/cli-v3/src/deploy/buildLogs.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,38 @@ describe("streamDeploymentEvents", () => {
258258
expect(onFinalized).toHaveBeenCalledTimes(1);
259259
});
260260

261+
it("stops consuming the stream once the finalized event arrives", async () => {
262+
let released = false;
263+
async function* recordsThenThrow() {
264+
try {
265+
yield {
266+
seqNum: 0,
267+
timestamp: 1_700_000_000_000,
268+
body: JSON.stringify({ type: "log", data: { message: "a" } }),
269+
};
270+
yield {
271+
seqNum: 1,
272+
timestamp: 1_700_000_000_000,
273+
body: JSON.stringify({ type: "finalized", data: { result: "succeeded" } }),
274+
};
275+
throw new Error("stream closed with unparsed data remaining");
276+
} finally {
277+
released = true;
278+
}
279+
}
280+
let finalizedCalls = 0;
281+
const final = await streamDeploymentEvents(
282+
recordsThenThrow(),
283+
{ started: false, log: () => {}, finish: () => {} },
284+
() => {
285+
finalizedCalls++;
286+
}
287+
);
288+
expect(final).toEqual({ result: "succeeded" });
289+
expect(finalizedCalls).toBe(1);
290+
expect(released).toBe(true);
291+
});
292+
261293
it("returns undefined when the stream ends without a finalized event", async () => {
262294
const final = await streamDeploymentEvents(
263295
records([JSON.stringify({ type: "log", data: { message: "a" } })]),

packages/cli-v3/src/deploy/buildLogs.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,6 @@ export async function streamDeploymentEvents(
158158
renderer: BuildLogRenderer,
159159
onFinalized: () => void
160160
): Promise<DeploymentFinalizedEvent["data"] | undefined> {
161-
let finalEvent: DeploymentFinalizedEvent["data"] | undefined;
162-
163161
for await (const record of records) {
164162
const result = DeploymentEventFromString.safeParse(record.body);
165163
if (!result.success) {
@@ -182,9 +180,8 @@ export async function streamDeploymentEvents(
182180
break;
183181
}
184182
case "finalized": {
185-
finalEvent = event.data;
186183
onFinalized();
187-
break;
184+
return event.data;
188185
}
189186
default: {
190187
event satisfies never;
@@ -193,5 +190,5 @@ export async function streamDeploymentEvents(
193190
}
194191
}
195192

196-
return finalEvent;
193+
return undefined;
197194
}

0 commit comments

Comments
 (0)