From 09ccfd338e2da9a5887df1fc47b74ef00823954d Mon Sep 17 00:00:00 2001 From: Shuveb Hussain Date: Thu, 27 Aug 2026 13:28:48 +0530 Subject: [PATCH] fix(server): honor the provider update opt-out when settings cannot be read The `enableProviderUpdateChecks` guard in `ModelManifest.refresh` skips the opt-out when settings are unreadable, so the manifest fetch goes out for a user who turned the setting off. Invert the guard so an unreadable setting resolves to not making the request. Co-Authored-By: Claude Opus 5 --- .../server/src/provider/ModelManifest.test.ts | 54 ++++++++++++++++++- apps/server/src/provider/ModelManifest.ts | 6 ++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/apps/server/src/provider/ModelManifest.test.ts b/apps/server/src/provider/ModelManifest.test.ts index fdcfa9335424..baea256eecc4 100644 --- a/apps/server/src/provider/ModelManifest.test.ts +++ b/apps/server/src/provider/ModelManifest.test.ts @@ -1,6 +1,10 @@ import { assert, describe, it } from "@effect/vitest"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { ProviderDriverKind, type ServerProviderModel } from "@t3tools/contracts"; +import { + ProviderDriverKind, + ServerSettingsError, + type ServerProviderModel, +} from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import { HttpClient, HttpClientResponse } from "effect/unstable/http"; @@ -119,6 +123,25 @@ const serviceLayers = (input: { Layer.provideMerge(httpClientLayer(input.response)), ); +/** + * Settings that cannot be read at all. `getSettings` carries a typed + * `ServerSettingsError`, and the live implementation reaches disk and the + * secret store, so an unreadable secret or a corrupt settings file lands here. + */ +const unreadableSettingsLayer = Layer.effect( + ServerSettings.ServerSettingsService, + Effect.map(ServerSettings.ServerSettingsService, (base) => ({ + ...base, + getSettings: Effect.fail( + new ServerSettingsError({ + settingsPath: "/nonexistent/settings.json", + operation: "read-secret", + cause: new Error("settings unreadable"), + }), + ), + })), +).pipe(Layer.provide(ServerSettings.layerTest({}))); + describe("ModelManifest service", () => { it.live("prefers a fetched manifest over the bundle and caches it to disk", () => Effect.gen(function* () { @@ -182,4 +205,33 @@ describe("ModelManifest service", () => { ), ), ); + + it.live("does not fetch when the opt-out setting cannot be read", () => + Effect.gen(function* () { + let fetchCount = 0; + const service = yield* make.pipe( + Effect.provide( + httpClientLayer(() => { + fetchCount += 1; + return Response.json(REMOTE_MANIFEST); + }), + ), + ); + assert.deepStrictEqual(yield* service.refresh, BUNDLED_MODEL_MANIFEST); + // `enableProviderUpdateChecks` is a don't-phone-home switch, so failing + // to read it has to resolve to not contacting the host. + assert.strictEqual(fetchCount, 0); + }).pipe( + Effect.scoped, + Effect.provide( + ServerConfig.layerTest(process.cwd(), { + prefix: "model-manifest-settings-unreadable-test", + }).pipe( + Layer.provideMerge(NodeServices.layer), + Layer.provideMerge(unreadableSettingsLayer), + Layer.provideMerge(httpClientLayer(() => Response.json(REMOTE_MANIFEST))), + ), + ), + ), + ); }); diff --git a/apps/server/src/provider/ModelManifest.ts b/apps/server/src/provider/ModelManifest.ts index cb9494992287..addba2f2033b 100644 --- a/apps/server/src/provider/ModelManifest.ts +++ b/apps/server/src/provider/ModelManifest.ts @@ -185,10 +185,14 @@ export const make = Effect.gen(function* () { // fetches only: a manifest already cached on disk from an earlier fetch // stays in effect, since the setting is about phoning home, not about // discarding data the server already holds. + // + // Unreadable settings skip the fetch too. This is a don't-phone-home + // switch, so "we could not tell whether the user opted out" has to resolve + // to not making the request. const settings = yield* settingsService.getSettings.pipe( Effect.catchCause(() => Effect.succeed(null)), ); - if (settings !== null && !settings.enableProviderUpdateChecks) return manifest; + if (settings === null || !settings.enableProviderUpdateChecks) return manifest; lastAttemptMs = now; const fetched = yield* httpClient.get(MODEL_MANIFEST_URL).pipe(