From ca788881fee9955edece3f926f5027037d2f68a5 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sat, 22 Aug 2026 18:08:30 +1000 Subject: [PATCH 1/2] fix: keep build errors out of GitHub statuses --- .../api/v1/agent/builds/[id]/status/route.ts | 4 +- web/tests/build-status-route.test.ts | 89 ++++++++++++++++++- 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/web/app/api/v1/agent/builds/[id]/status/route.ts b/web/app/api/v1/agent/builds/[id]/status/route.ts index f43afea4..cafd15ed 100644 --- a/web/app/api/v1/agent/builds/[id]/status/route.ts +++ b/web/app/api/v1/agent/builds/[id]/status/route.ts @@ -283,7 +283,7 @@ export async function POST( update.status === "completed" ? "Preview image built; preparing deployment" : update.status === "failed" - ? update.error || "Preview build failed" + ? "Preview build failed" : `Preview build ${update.status}...`, logUrl, }); @@ -325,7 +325,7 @@ export async function POST( build.githubDeploymentId, "failure", { - description: update.error || "Build failed", + description: "Build failed", logUrl, environmentUrl, }, diff --git a/web/tests/build-status-route.test.ts b/web/tests/build-status-route.test.ts index ee7b7eba..ec99d8b5 100644 --- a/web/tests/build-status-route.test.ts +++ b/web/tests/build-status-route.test.ts @@ -144,11 +144,17 @@ function post( imageUri: string | null | undefined = status === "completed" ? amd64Image : undefined, + error?: string, ) { return POST( new Request("http://localhost/api/v1/agent/builds/build-amd64/status", { method: "POST", - body: JSON.stringify({ status, resolvedCommitSha: commitSha, imageUri }), + body: JSON.stringify({ + status, + resolvedCommitSha: commitSha, + imageUri, + error, + }), }) as NextRequest, { params: Promise.resolve({ id: "build-amd64" }) }, ); @@ -247,6 +253,51 @@ describe("agent build status transitions", () => { ); }); + it("keeps detailed build errors out of GitHub deployment statuses", async () => { + const error = `buildctl build failed:\n${"internal output ".repeat(20)}`; + const githubSpecification = { + ...specification, + source: { + ...specification.source, + authentication: { type: "github_app" as const, installationId: 123 }, + }, + }; + const failedBuild = build("failed", { githubDeploymentId: 456, error }); + mocks.selectResults.push( + [build("building", { githubDeploymentId: 456 })], + [ + { + specification: githubSpecification, + projectSlug: "cloud", + environmentName: "production", + }, + ], + ); + mocks.updateResults.push([failedBuild]); + + expect((await post("failed", undefined, error)).status).toBe(200); + expect(mocks.updateSets[0]).toMatchObject({ status: "failed", error }); + expect(mocks.notify).toHaveBeenCalledWith( + expect.objectContaining({ error }), + ); + expect(mocks.createBuildCompleted).toHaveBeenCalledWith( + expect.objectContaining({ status: "failed", error }), + { id: "build-completed-build-amd64" }, + ); + expect(mocks.updateGitHubDeploymentStatus).toHaveBeenCalledWith( + 123, + "acme/app", + 456, + "failure", + { + description: "Build failed", + logUrl: "https://cloud.techulus.com/builds/build-amd64/logs", + environmentUrl: + "https://cloud.techulus.com/dashboard/projects/cloud/production/services/service-1", + }, + ); + }); + it("stores completion and the platform artifact atomically", async () => { const completedBuild = build("completed", { imageUri: amd64Image, @@ -368,6 +419,42 @@ describe("agent build status transitions", () => { expect(mocks.updateGitHubDeploymentStatus).not.toHaveBeenCalled(); }); + it("keeps detailed build errors out of preview GitHub statuses", async () => { + const error = `buildctl build failed:\n${"internal output ".repeat(20)}`; + const previewSpecification = { + ...specification, + source: { + ...specification.source, + authentication: { type: "github_app" as const, installationId: 123 }, + }, + }; + const failedBuild = build("failed", { githubDeploymentId: 456, error }); + mocks.selectResults.push( + [build("building", { githubDeploymentId: 456 })], + [ + { + specification: previewSpecification, + projectSlug: "cloud", + environmentName: "production", + previewOfService: "base-service", + }, + ], + ); + mocks.updateResults.push([failedBuild]); + + expect((await post("failed", undefined, error)).status).toBe(200); + expect(mocks.updatePreviewGitHubStatus).toHaveBeenCalledWith({ + serviceId: "service-1", + serviceRevisionId: "revision-1", + expectedDeploymentId: 456, + state: "failure", + description: "Preview build failed", + logUrl: + "https://cloud.techulus.com/dashboard/projects/cloud/production/services/service-1/builds/build-amd64", + }); + expect(mocks.updateGitHubDeploymentStatus).not.toHaveBeenCalled(); + }); + it("does not enqueue manifest work after the service is deleted", async () => { const completedBuild = build("completed", { imageUri: amd64Image }); mocks.selectResults.push( From bae462909034868edea6ee5f08263dcd5d1e441d Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sat, 22 Aug 2026 18:17:50 +1000 Subject: [PATCH 2/2] test: remove redundant GitHub status coverage --- web/tests/build-status-route.test.ts | 89 +--------------------------- 1 file changed, 1 insertion(+), 88 deletions(-) diff --git a/web/tests/build-status-route.test.ts b/web/tests/build-status-route.test.ts index ec99d8b5..ee7b7eba 100644 --- a/web/tests/build-status-route.test.ts +++ b/web/tests/build-status-route.test.ts @@ -144,17 +144,11 @@ function post( imageUri: string | null | undefined = status === "completed" ? amd64Image : undefined, - error?: string, ) { return POST( new Request("http://localhost/api/v1/agent/builds/build-amd64/status", { method: "POST", - body: JSON.stringify({ - status, - resolvedCommitSha: commitSha, - imageUri, - error, - }), + body: JSON.stringify({ status, resolvedCommitSha: commitSha, imageUri }), }) as NextRequest, { params: Promise.resolve({ id: "build-amd64" }) }, ); @@ -253,51 +247,6 @@ describe("agent build status transitions", () => { ); }); - it("keeps detailed build errors out of GitHub deployment statuses", async () => { - const error = `buildctl build failed:\n${"internal output ".repeat(20)}`; - const githubSpecification = { - ...specification, - source: { - ...specification.source, - authentication: { type: "github_app" as const, installationId: 123 }, - }, - }; - const failedBuild = build("failed", { githubDeploymentId: 456, error }); - mocks.selectResults.push( - [build("building", { githubDeploymentId: 456 })], - [ - { - specification: githubSpecification, - projectSlug: "cloud", - environmentName: "production", - }, - ], - ); - mocks.updateResults.push([failedBuild]); - - expect((await post("failed", undefined, error)).status).toBe(200); - expect(mocks.updateSets[0]).toMatchObject({ status: "failed", error }); - expect(mocks.notify).toHaveBeenCalledWith( - expect.objectContaining({ error }), - ); - expect(mocks.createBuildCompleted).toHaveBeenCalledWith( - expect.objectContaining({ status: "failed", error }), - { id: "build-completed-build-amd64" }, - ); - expect(mocks.updateGitHubDeploymentStatus).toHaveBeenCalledWith( - 123, - "acme/app", - 456, - "failure", - { - description: "Build failed", - logUrl: "https://cloud.techulus.com/builds/build-amd64/logs", - environmentUrl: - "https://cloud.techulus.com/dashboard/projects/cloud/production/services/service-1", - }, - ); - }); - it("stores completion and the platform artifact atomically", async () => { const completedBuild = build("completed", { imageUri: amd64Image, @@ -419,42 +368,6 @@ describe("agent build status transitions", () => { expect(mocks.updateGitHubDeploymentStatus).not.toHaveBeenCalled(); }); - it("keeps detailed build errors out of preview GitHub statuses", async () => { - const error = `buildctl build failed:\n${"internal output ".repeat(20)}`; - const previewSpecification = { - ...specification, - source: { - ...specification.source, - authentication: { type: "github_app" as const, installationId: 123 }, - }, - }; - const failedBuild = build("failed", { githubDeploymentId: 456, error }); - mocks.selectResults.push( - [build("building", { githubDeploymentId: 456 })], - [ - { - specification: previewSpecification, - projectSlug: "cloud", - environmentName: "production", - previewOfService: "base-service", - }, - ], - ); - mocks.updateResults.push([failedBuild]); - - expect((await post("failed", undefined, error)).status).toBe(200); - expect(mocks.updatePreviewGitHubStatus).toHaveBeenCalledWith({ - serviceId: "service-1", - serviceRevisionId: "revision-1", - expectedDeploymentId: 456, - state: "failure", - description: "Preview build failed", - logUrl: - "https://cloud.techulus.com/dashboard/projects/cloud/production/services/service-1/builds/build-amd64", - }); - expect(mocks.updateGitHubDeploymentStatus).not.toHaveBeenCalled(); - }); - it("does not enqueue manifest work after the service is deleted", async () => { const completedBuild = build("completed", { imageUri: amd64Image }); mocks.selectResults.push(