From d6d0ac529384aa263b81e01951d88fa599929a17 Mon Sep 17 00:00:00 2001 From: MarcoDotIO Date: Mon, 2 Nov 2026 11:40:37 -0500 Subject: [PATCH] fix(cli): t3 update no longer calls a failed download a missing release Co-Authored-By: Claude Opus 5.5 --- apps/server/src/cli/update.test.ts | 79 +++++++++++++++++++++++++++++- apps/server/src/cli/update.ts | 11 ++++- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/apps/server/src/cli/update.test.ts b/apps/server/src/cli/update.test.ts index 848c2c798df41..d526bd02a19ac 100644 --- a/apps/server/src/cli/update.test.ts +++ b/apps/server/src/cli/update.test.ts @@ -10,7 +10,7 @@ import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as TestClock from "effect/testing/TestClock"; -import { HttpClient, HttpClientResponse } from "effect/http"; +import { HttpClient, HttpClientError, HttpClientResponse } from "effect/http"; import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; import { afterEach, vi } from "vite-plus/test"; @@ -120,6 +120,83 @@ it.layer(NodeServices.layer)("t3 update under a managed policy", (it) => { ); }); +it.layer(NodeServices.layer)("t3 update download failures", (it) => { + const updateFailure = (httpClient: HttpClient.HttpClient) => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-update-" }); + return yield* runUpdate({ + baseDir, + logsDir: baseDir, + serverRuntimeStatePath: path.join(baseDir, "server-runtime.json"), + channel: undefined, + // The checksums URL carries the version, so it contains "404" whatever the failure. + requestedVersion: "1.2.3-nightly.20260404.1", + allowDowngrade: true, + assumeYes: true, + }).pipe( + Effect.flip, + Effect.provideService(HostProcess.Platform, "linux"), + Effect.provideService(HostProcess.Architecture, "x64"), + Effect.provide( + Layer.mergeAll( + ManagedPolicy.layerTest(), + Layer.mock(BootService.BootService, { + status: Effect.succeed({ + supported: false, + installed: false, + current: false, + unitPath: "", + logPath: "", + }), + }), + Layer.mock(ProcessRunner.ProcessRunner, {}), + Layer.succeed(HttpClient.HttpClient, httpClient), + ), + ), + Effect.scoped, + ); + }); + + const respondWith = (status: number) => + HttpClient.make((request) => + Effect.succeed(HttpClientResponse.fromWeb(request, new Response("", { status }))), + ); + + it.effect("reports a missing release when the checksums return 404", () => + Effect.gen(function* () { + const error = yield* updateFailure(respondWith(404)); + assert.equal( + error.message, + "No release archive was published for t3@1.2.3-nightly.20260404.1.", + ); + }), + ); + + it.effect("does not call a failed download a missing release", () => + Effect.gen(function* () { + const reset = HttpClient.make((request) => + Effect.fail( + new HttpClientError.HttpClientError({ + reason: new HttpClientError.TransportError({ + request, + cause: new Error("read ECONNRESET"), + }), + }), + ), + ); + for (const httpClient of [reset, respondWith(503)]) { + const error = yield* updateFailure(httpClient); + assert.equal( + error.message, + "Pinned runtime install failed while downloading the t3 release checksums.", + ); + } + }), + ); +}); + it.layer(NodeServices.layer)("t3 update launcher", (it) => { it.effect("repoints a symlink that lives in a runtime versions tree", () => Effect.gen(function* () { diff --git a/apps/server/src/cli/update.ts b/apps/server/src/cli/update.ts index dc2e29345864c..83a58b7bc84be 100644 --- a/apps/server/src/cli/update.ts +++ b/apps/server/src/cli/update.ts @@ -16,7 +16,13 @@ import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; import { Argument, Command, Flag, GlobalFlag, Prompt } from "effect/cli"; -import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http"; +import { + FetchHttpClient, + HttpClient, + HttpClientError, + HttpClientRequest, + HttpClientResponse, +} from "effect/http"; import packageJson from "../../package.json" with { type: "json" }; import * as BootService from "../cloud/bootService.ts"; @@ -485,7 +491,8 @@ export const runUpdate = Effect.fn("cli.update.run")(function* (input: { (error): error is PinnedRuntimeInstallError => error._tag === "PinnedRuntimeInstallError" && error.step.startsWith("downloading the t3 release checksums") && - String(error.cause).includes("404"), + HttpClientError.isHttpClientError(error.cause) && + error.cause.response?.status === 404, () => Effect.fail( new CliUpdateError({