mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-03 03:03:24 -04: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.
|
||||
- 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.
|
||||
- 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: 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.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveStateDir } from "./paths.js";
|
||||
import { withEnvOverride, withTempHome } from "./test-helpers.js";
|
||||
|
||||
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 path from "node:path";
|
||||
import type { OpenClawConfig, ConfigFileSnapshot, LegacyConfigIssue } from "./types.js";
|
||||
import { loadDotEnv } from "../infra/dotenv.js";
|
||||
import { resolveRequiredHomeDir } from "../infra/home-dir.js";
|
||||
import {
|
||||
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(
|
||||
raw: string,
|
||||
json5: { parse: (value: string) => unknown } = JSON5,
|
||||
@@ -213,6 +223,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
||||
|
||||
function loadConfig(): OpenClawConfig {
|
||||
try {
|
||||
maybeLoadDotEnvForConfig(deps.env);
|
||||
if (!deps.fs.existsSync(configPath)) {
|
||||
if (shouldEnableShellEnvFallback(deps.env) && !shouldDeferShellEnvFallback(deps.env)) {
|
||||
loadShellEnvFallback({
|
||||
@@ -323,6 +334,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
||||
}
|
||||
|
||||
async function readConfigFileSnapshot(): Promise<ConfigFileSnapshot> {
|
||||
maybeLoadDotEnvForConfig(deps.env);
|
||||
const exists = deps.fs.existsSync(configPath);
|
||||
if (!exists) {
|
||||
const hash = hashConfigRaw(null);
|
||||
|
||||
Reference in New Issue
Block a user