Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/deploy-log-stream-disconnect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---

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.
50 changes: 38 additions & 12 deletions packages/cli-v3/src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { resolveAlwaysExternal } from "../build/externals.js";
import { createContextArchive, getArchiveSize } from "../deploy/archiveContext.js";
import { createBundleArchive } from "../deploy/bundleArchive.js";
import {
type BuildLogRenderer,
BuildLogsMode,
createBuildLogRenderer,
resolveBuildLogsMode,
Expand Down Expand Up @@ -2284,6 +2285,36 @@ function showFullBuildLogs(options: DeployCommandOptions) {
return resolveBuildLogsMode(options.buildLogs, buildLogsEnv(options)) === "full";
}

function buildLogStreamError(
error: unknown,
renderer: BuildLogRenderer,
deployment: Pick<InitializeDeploymentResponseBody, "version">,
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,
Expand Down Expand Up @@ -2319,22 +2350,17 @@ 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 finalDeploymentEvent = await streamDeploymentEvents(readSession, renderer, () =>
abortController.abort()
const [streamError, finalDeploymentEvent] = await tryCatch(
streamDeploymentEvents(readSession, renderer, () => abortController.abort())
);

if (streamError) {
throw buildLogStreamError(streamError, renderer, 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
Expand Down
32 changes: 32 additions & 0 deletions packages/cli-v3/src/deploy/buildLogs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,38 @@ 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;
}
}
let finalizedCalls = 0;
const final = await streamDeploymentEvents(
recordsThenThrow(),
{ started: false, log: () => {}, finish: () => {} },
() => {
finalizedCalls++;
}
);
expect(final).toEqual({ result: "succeeded" });
expect(finalizedCalls).toBe(1);
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" } })]),
Expand Down
7 changes: 2 additions & 5 deletions packages/cli-v3/src/deploy/buildLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,6 @@ export async function streamDeploymentEvents(
renderer: BuildLogRenderer,
onFinalized: () => void
): Promise<DeploymentFinalizedEvent["data"] | undefined> {
let finalEvent: DeploymentFinalizedEvent["data"] | undefined;

for await (const record of records) {
const result = DeploymentEventFromString.safeParse(record.body);
if (!result.success) {
Expand All @@ -182,9 +180,8 @@ export async function streamDeploymentEvents(
break;
}
case "finalized": {
finalEvent = event.data;
onFinalized();
break;
return event.data;
Comment thread
myftija marked this conversation as resolved.
}
default: {
event satisfies never;
Expand All @@ -193,5 +190,5 @@ export async function streamDeploymentEvents(
}
}

return finalEvent;
return undefined;
}
Loading