diff --git a/apps/server/src/config.ts b/apps/server/src/config.ts index 0b25b3ccc..34f19fabd 100644 --- a/apps/server/src/config.ts +++ b/apps/server/src/config.ts @@ -1,7 +1,28 @@ -import { existsSync } from "node:fs"; +import { existsSync, readFileSync } from "node:fs"; import { resolve } from "node:path"; +import { parseEnv } from "node:util"; -if (existsSync(".env")) process.loadEnvFile(".env"); +/** .env keys whose file value loses to a different value already set in the environment. */ +export function shadowedEnvKeys( + file: Record, + env: Record = process.env, +): string[] { + return Object.keys(file).filter((key) => env[key] !== undefined && env[key] !== file[key]); +} + +if (existsSync(".env")) { + // loadEnvFile never overrides existing variables. A stale shell or system-wide value + // (for example OPENAI_API_KEY) would otherwise silently replace the .env setting. + const shadowed = shadowedEnvKeys(parseEnv(readFileSync(".env", "utf8"))); + process.loadEnvFile(".env"); + if (shadowed.length) + console.warn( + `[OpenMuse] Using ${shadowed.join(", ")} from the environment instead of .env. ` + + (shadowed.length === 1 + ? "Unset it to use the .env value." + : "Unset them to use the .env values."), + ); +} process.env.DO_NOT_TRACK ??= "1"; process.env.COPILOTKIT_TELEMETRY_DISABLED ??= "true"; diff --git a/tests/config.test.ts b/tests/config.test.ts index aab558a12..568eeeac7 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -1,6 +1,10 @@ import assert from "node:assert/strict"; import { test } from "node:test"; -import { assertApiDeploymentConfig, type Config } from "../apps/server/src/config.ts"; +import { + assertApiDeploymentConfig, + type Config, + shadowedEnvKeys, +} from "../apps/server/src/config.ts"; const sampleConfig: Config = { mode: "sample", @@ -46,3 +50,10 @@ test("every API mode accepts a non-empty Intelligence key", () => { ); } }); + +test("environment variables that override a different .env value are reported by name", () => { + const file = { OPENAI_API_KEY: "sk-or-file", MODEL: "openai/gpt-5", PORT: "8787", EMPTY: "" }; + const env = { OPENAI_API_KEY: "sk-proj-system", MODEL: "openai/gpt-5", EMPTY: "set" }; + assert.deepEqual(shadowedEnvKeys(file, env), ["OPENAI_API_KEY", "EMPTY"]); + assert.deepEqual(shadowedEnvKeys(file, {}), []); +});