mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 06:21:36 +00:00
(fix): .env vars not available during runtime config reloads (healthchecks fail with MissingEnvVarError) (#12748)
* Config: reload dotenv before env substitution on runtime loads * Test: isolate config env var regression from host state env * fix: keep dotenv vars resolvable on runtime config reloads (#12748) (thanks @rodrigouroz) --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -30,6 +30,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Tools/web_search: normalize direct Perplexity model IDs while keeping OpenRouter model IDs unchanged. (#12795) Thanks @cdorsey.
|
- Tools/web_search: normalize direct Perplexity model IDs while keeping OpenRouter model IDs unchanged. (#12795) Thanks @cdorsey.
|
||||||
- Model failover: treat HTTP 400 errors as failover-eligible, enabling automatic model fallback. (#1879) Thanks @orenyomtov.
|
- Model failover: treat HTTP 400 errors as failover-eligible, enabling automatic model fallback. (#1879) Thanks @orenyomtov.
|
||||||
- Errors: prevent false positive context overflow detection when conversation mentions "context overflow" topic. (#2078) Thanks @sbking.
|
- Errors: prevent false positive context overflow detection when conversation mentions "context overflow" topic. (#2078) Thanks @sbking.
|
||||||
|
- Config: re-hydrate state-dir `.env` during runtime config loads so `${VAR}` substitutions remain resolvable. (#12748) Thanks @rodrigouroz.
|
||||||
- Gateway: no more post-compaction amnesia; injected transcript writes now preserve Pi session `parentId` chain so agents can remember again. (#12283) Thanks @Takhoffman.
|
- Gateway: no more post-compaction amnesia; injected transcript writes now preserve Pi session `parentId` chain so agents can remember again. (#12283) Thanks @Takhoffman.
|
||||||
- Gateway: fix multi-agent sessions.usage discovery. (#11523) Thanks @Takhoffman.
|
- Gateway: fix multi-agent sessions.usage discovery. (#11523) Thanks @Takhoffman.
|
||||||
- Agents: recover from context overflow caused by oversized tool results (pre-emptive capping + fallback truncation). (#11579) Thanks @tyler6204.
|
- Agents: recover from context overflow caused by oversized tool results (pre-emptive capping + fallback truncation). (#11579) Thanks @tyler6204.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { resolveStateDir } from "./paths.js";
|
||||||
import { withEnvOverride, withTempHome } from "./test-helpers.js";
|
import { withEnvOverride, withTempHome } from "./test-helpers.js";
|
||||||
|
|
||||||
describe("config env vars", () => {
|
describe("config env vars", () => {
|
||||||
@@ -75,4 +76,50 @@ describe("config env vars", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("loads ${VAR} substitutions from ~/.openclaw/.env on repeated runtime loads", async () => {
|
||||||
|
await withTempHome(async (home) => {
|
||||||
|
await withEnvOverride(
|
||||||
|
{
|
||||||
|
OPENCLAW_STATE_DIR: path.join(home, ".openclaw"),
|
||||||
|
CLAWDBOT_STATE_DIR: undefined,
|
||||||
|
OPENCLAW_HOME: undefined,
|
||||||
|
CLAWDBOT_HOME: undefined,
|
||||||
|
BRAVE_API_KEY: undefined,
|
||||||
|
OPENCLAW_DISABLE_CONFIG_CACHE: "1",
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
const configDir = resolveStateDir(process.env, () => home);
|
||||||
|
await fs.mkdir(configDir, { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(configDir, "openclaw.json"),
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
tools: {
|
||||||
|
web: {
|
||||||
|
search: {
|
||||||
|
apiKey: "${BRAVE_API_KEY}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
await fs.writeFile(path.join(configDir, ".env"), "BRAVE_API_KEY=from-dotenv\n", "utf-8");
|
||||||
|
|
||||||
|
const { loadConfig } = await import("./config.js");
|
||||||
|
|
||||||
|
const first = loadConfig();
|
||||||
|
expect(first.tools?.web?.search?.apiKey).toBe("from-dotenv");
|
||||||
|
|
||||||
|
delete process.env.BRAVE_API_KEY;
|
||||||
|
const second = loadConfig();
|
||||||
|
expect(second.tools?.web?.search?.apiKey).toBe("from-dotenv");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import fs from "node:fs";
|
|||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { OpenClawConfig, ConfigFileSnapshot, LegacyConfigIssue } from "./types.js";
|
import type { OpenClawConfig, ConfigFileSnapshot, LegacyConfigIssue } from "./types.js";
|
||||||
|
import { loadDotEnv } from "../infra/dotenv.js";
|
||||||
import { resolveRequiredHomeDir } from "../infra/home-dir.js";
|
import { resolveRequiredHomeDir } from "../infra/home-dir.js";
|
||||||
import {
|
import {
|
||||||
loadShellEnvFallback,
|
loadShellEnvFallback,
|
||||||
@@ -191,6 +192,15 @@ function normalizeDeps(overrides: ConfigIoDeps = {}): Required<ConfigIoDeps> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function maybeLoadDotEnvForConfig(env: NodeJS.ProcessEnv): void {
|
||||||
|
// Only hydrate dotenv for the real process env. Callers using injected env
|
||||||
|
// objects (tests/diagnostics) should stay isolated.
|
||||||
|
if (env !== process.env) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loadDotEnv({ quiet: true });
|
||||||
|
}
|
||||||
|
|
||||||
export function parseConfigJson5(
|
export function parseConfigJson5(
|
||||||
raw: string,
|
raw: string,
|
||||||
json5: { parse: (value: string) => unknown } = JSON5,
|
json5: { parse: (value: string) => unknown } = JSON5,
|
||||||
@@ -213,6 +223,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
|||||||
|
|
||||||
function loadConfig(): OpenClawConfig {
|
function loadConfig(): OpenClawConfig {
|
||||||
try {
|
try {
|
||||||
|
maybeLoadDotEnvForConfig(deps.env);
|
||||||
if (!deps.fs.existsSync(configPath)) {
|
if (!deps.fs.existsSync(configPath)) {
|
||||||
if (shouldEnableShellEnvFallback(deps.env) && !shouldDeferShellEnvFallback(deps.env)) {
|
if (shouldEnableShellEnvFallback(deps.env) && !shouldDeferShellEnvFallback(deps.env)) {
|
||||||
loadShellEnvFallback({
|
loadShellEnvFallback({
|
||||||
@@ -323,6 +334,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function readConfigFileSnapshot(): Promise<ConfigFileSnapshot> {
|
async function readConfigFileSnapshot(): Promise<ConfigFileSnapshot> {
|
||||||
|
maybeLoadDotEnvForConfig(deps.env);
|
||||||
const exists = deps.fs.existsSync(configPath);
|
const exists = deps.fs.existsSync(configPath);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
const hash = hashConfigRaw(null);
|
const hash = hashConfigRaw(null);
|
||||||
|
|||||||
Reference in New Issue
Block a user