From 53a477c2e91d206057adc5ac4c28cbdaddfbc07c Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 20 Jun 2026 11:11:43 -0700 Subject: [PATCH] [codex] Structure desktop backend settings read errors (#3379) Co-authored-by: codex --- .../DesktopBackendConfiguration.test.ts | 62 +++++++++++++++++++ .../backend/DesktopBackendConfiguration.ts | 51 ++++++++++----- 2 files changed, 98 insertions(+), 15 deletions(-) diff --git a/apps/desktop/src/backend/DesktopBackendConfiguration.test.ts b/apps/desktop/src/backend/DesktopBackendConfiguration.test.ts index cb68b2cd47fc42c7ccef5b65dd13679a7c5e57ad..43e77a0c4cb100ca05f8a7dbb24de16007c3f516 100644 --- a/apps/desktop/src/backend/DesktopBackendConfiguration.test.ts +++ b/apps/desktop/src/backend/DesktopBackendConfiguration.test.ts @@ -3,6 +3,8 @@ import { assert, describe, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; +import * as Logger from "effect/Logger"; +import * as PlatformError from "effect/PlatformError"; import * as Schema from "effect/Schema"; import * as DesktopEnvironment from "../app/DesktopEnvironment.ts"; @@ -21,6 +23,10 @@ const encodePersistedServerObservabilitySettingsDocument = Schema.encodeEffect( Schema.fromJsonString(PersistedServerObservabilitySettingsDocument), ); +const isDesktopBackendObservabilitySettingsReadError = Schema.is( + DesktopBackendConfiguration.DesktopBackendObservabilitySettingsReadError, +); + const serverExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExposure, { getState: Effect.die("unexpected getState"), backendConfig: Effect.succeed({ @@ -166,6 +172,62 @@ describe("DesktopBackendConfiguration", () => { ), ); + it.effect("logs structured context when persisted observability settings cannot be read", () => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const baseDir = yield* fileSystem.makeTempDirectoryScoped({ + prefix: "t3-desktop-backend-config-test-", + }); + const settingsPath = `${baseDir}/userdata/settings.json`; + const cause = PlatformError.systemError({ + _tag: "PermissionDenied", + module: "FileSystem", + method: "readFileString", + pathOrDescriptor: settingsPath, + }); + const messages: Array = []; + const logger = Logger.make(({ message }) => { + messages.push(message); + }); + const failingFileSystemLayer = Layer.succeed( + FileSystem.FileSystem, + FileSystem.makeNoop({ + readFileString: () => Effect.fail(cause), + }), + ); + + const config = yield* Effect.gen(function* () { + const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration; + return yield* configuration.resolve; + }).pipe( + Effect.provide( + Layer.mergeAll( + DesktopBackendConfiguration.layer.pipe( + Layer.provideMerge(serverExposureLayer), + Layer.provideMerge(makeEnvironmentLayer(baseDir)), + Layer.provideMerge(failingFileSystemLayer), + ), + Logger.layer([logger], { mergeWithExisting: false }), + ), + ), + ); + + assert.isUndefined(config.bootstrap.otlpTracesUrl); + assert.isUndefined(config.bootstrap.otlpMetricsUrl); + + const error = messages + .flatMap((message) => (Array.isArray(message) ? message : [message])) + .find(isDesktopBackendObservabilitySettingsReadError); + assert.isDefined(error); + assert.equal(error.settingsPath, settingsPath); + assert.equal(error.cause, cause); + assert.equal( + error.message, + `Failed to read persisted backend observability settings at ${settingsPath}.`, + ); + }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), + ); + it.effect("captures backend output in development so child process logs can be persisted", () => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; diff --git a/apps/desktop/src/backend/DesktopBackendConfiguration.ts b/apps/desktop/src/backend/DesktopBackendConfiguration.ts index ec72faf910b6c7f27c0e9b787ed976c69136df44..d8bd1a13dcb063e4fea3cd60712c869160e9a785 100644 --- a/apps/desktop/src/backend/DesktopBackendConfiguration.ts +++ b/apps/desktop/src/backend/DesktopBackendConfiguration.ts @@ -8,12 +8,24 @@ import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as PlatformError from "effect/PlatformError"; import * as Ref from "effect/Ref"; +import * as Schema from "effect/Schema"; import * as DesktopBackendManager from "./DesktopBackendManager.ts"; import * as DesktopEnvironment from "../app/DesktopEnvironment.ts"; -import * as DesktopObservability from "../app/DesktopObservability.ts"; import * as DesktopServerExposure from "./DesktopServerExposure.ts"; +export class DesktopBackendObservabilitySettingsReadError extends Schema.TaggedErrorClass()( + "DesktopBackendObservabilitySettingsReadError", + { + settingsPath: Schema.String, + cause: Schema.Defect(), + }, +) { + override get message(): string { + return `Failed to read persisted backend observability settings at ${this.settingsPath}.`; + } +} + export class DesktopBackendConfiguration extends Context.Service< DesktopBackendConfiguration, { @@ -50,25 +62,34 @@ const DESKTOP_BACKEND_ENV_NAMES = [ const backendChildEnvPatch = (): Record => Object.fromEntries(DESKTOP_BACKEND_ENV_NAMES.map((name) => [name, undefined])); -const { logWarning: logBackendConfigurationWarning } = DesktopObservability.makeComponentLogger( - "desktop-backend-configuration", -); +const logBackendObservabilitySettingsReadFailure = ( + settingsPath: string, + cause: PlatformError.PlatformError, +) => { + const error = new DesktopBackendObservabilitySettingsReadError({ settingsPath, cause }); + return Effect.logWarning(error).pipe( + Effect.annotateLogs({ + component: "desktop-backend-configuration", + error, + }), + ); +}; const readPersistedBackendObservabilitySettings = Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const environment = yield* DesktopEnvironment.DesktopEnvironment; - const exists = yield* fileSystem - .exists(environment.serverSettingsPath) - .pipe(Effect.orElseSucceed(() => false)); - if (!exists) { - return emptyBackendObservabilitySettings; - } - - const raw = yield* fileSystem.readFileString(environment.serverSettingsPath).pipe(Effect.option); + const raw = yield* fileSystem.readFileString(environment.serverSettingsPath).pipe( + Effect.map(Option.some), + Effect.catchTags({ + PlatformError: (cause) => + cause.reason._tag === "NotFound" + ? Effect.succeed(Option.none()) + : logBackendObservabilitySettingsReadFailure(environment.serverSettingsPath, cause).pipe( + Effect.as(Option.none()), + ), + }), + ); if (Option.isNone(raw)) { - yield* logBackendConfigurationWarning( - "failed to read persisted backend observability settings", - ); return emptyBackendObservabilitySettings; } -- 2.54.0