From b90baaff71fd985a0e91bc9d24f5ba76b9300d71 Mon Sep 17 00:00:00 2001 From: Matt Cowger Date: Thu, 18 Jun 2026 20:09:39 -0700 Subject: [PATCH] fix: daemon warns instead of crashing on missing OpenAI speech credentials (#1368) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: daemon warns instead of crashing on missing OpenAI speech credentials validateOpenAiCredentialRequirements was throwing an error when OpenAI speech providers were configured without credentials, causing the daemon to crash before it could start serving health checks. Changed to log a warning instead — the daemon starts successfully and reports speech services as unavailable. Closes #1367 * chore: address Greptile review feedback - Remove duplicate warning in initializeOpenAiSpeechServices when all credentials are missing (already warned by validate) - Strengthen test assertions: check getListenTarget() after start - Wrap daemon start/stop in try/finally for proper cleanup on failure --- packages/server/src/server/bootstrap.smoke.test.ts | 13 +++++++++---- .../src/server/speech/providers/openai/runtime.ts | 9 +++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/server/src/server/bootstrap.smoke.test.ts b/packages/server/src/server/bootstrap.smoke.test.ts index 113240ded..d48a6b1c0 100644 --- a/packages/server/src/server/bootstrap.smoke.test.ts +++ b/packages/server/src/server/bootstrap.smoke.test.ts @@ -339,7 +339,7 @@ describe("paseo daemon bootstrap", () => { } }); - test("fails fast when OpenAI speech provider is configured without credentials", async () => { + test("starts when OpenAI speech provider is configured without credentials", async () => { const paseoHomeRoot = await mkdtemp(path.join(os.tmpdir(), "paseo-openai-config-")); const paseoHome = path.join(paseoHomeRoot, ".paseo"); const staticDir = await mkdtemp(path.join(os.tmpdir(), "paseo-static-")); @@ -368,9 +368,14 @@ describe("paseo daemon bootstrap", () => { }; try { - await expect(createPaseoDaemon(config, pino({ level: "silent" }))).rejects.toThrow( - "Missing OpenAI credentials", - ); + const daemon = await createPaseoDaemon(config, pino({ level: "silent" })); + try { + await daemon.start(); + expect(daemon.getListenTarget()).toBeDefined(); + // Must also stop without throwing + } finally { + await daemon.stop(); + } } finally { await rm(paseoHomeRoot, { recursive: true, force: true }); await rm(staticDir, { recursive: true, force: true }); diff --git a/packages/server/src/server/speech/providers/openai/runtime.ts b/packages/server/src/server/speech/providers/openai/runtime.ts index 79a0585c2..dfd68563c 100644 --- a/packages/server/src/server/speech/providers/openai/runtime.ts +++ b/packages/server/src/server/speech/providers/openai/runtime.ts @@ -85,7 +85,7 @@ export function validateOpenAiCredentialRequirements(params: { } if (missingOpenAiCredentialsFor.length > 0) { - logger.error( + logger.warn( { requestedProviders: { dictationStt: providers.dictationStt.provider, @@ -94,10 +94,7 @@ export function validateOpenAiCredentialRequirements(params: { }, missingOpenAiCredentialsFor, }, - "Invalid speech configuration: OpenAI provider selected but credentials are missing", - ); - throw new Error( - `Missing OpenAI credentials for configured speech features: ${missingOpenAiCredentialsFor.join(", ")}`, + "Invalid speech configuration: OpenAI provider selected but credentials are missing — speech features will be unavailable", ); } } @@ -195,7 +192,7 @@ export function initializeOpenAiSpeechServices(params: { ); } } else if (needsAnyOpenAi) { - logger.warn("OpenAI speech providers are configured but credentials are missing"); + // validateOpenAiCredentialRequirements already warned about missing credentials } return {