mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:40:42 +00:00
fix: avoid duplicate gateway config loads
This commit is contained in:
@@ -27,6 +27,10 @@ const configState = vi.hoisted(() => ({
|
||||
cfg: {} as Record<string, unknown>,
|
||||
snapshot: { exists: false } as Record<string, unknown>,
|
||||
}));
|
||||
const readBestEffortConfig = vi.fn(async () => configState.cfg);
|
||||
const readConfigFileSnapshotWithPluginMetadata = vi.fn(async () => ({
|
||||
snapshot: configState.snapshot,
|
||||
}));
|
||||
const recoverConfigFromLastKnownGood = vi.fn<(params?: unknown) => Promise<boolean>>(
|
||||
async (_params?: unknown) => false,
|
||||
);
|
||||
@@ -52,8 +56,9 @@ const { runtimeErrors, defaultRuntime, resetRuntimeCapture } = createCliRuntimeC
|
||||
|
||||
vi.mock("../../config/config.js", () => ({
|
||||
getConfigPath: () => "/tmp/openclaw-test-missing-config.json",
|
||||
readBestEffortConfig: async () => configState.cfg,
|
||||
readBestEffortConfig: () => readBestEffortConfig(),
|
||||
readConfigFileSnapshot: async () => configState.snapshot,
|
||||
readConfigFileSnapshotWithPluginMetadata: () => readConfigFileSnapshotWithPluginMetadata(),
|
||||
recoverConfigFromLastKnownGood: (params: unknown) => recoverConfigFromLastKnownGood(params),
|
||||
recoverConfigFromJsonRootSuffix: (snapshot: unknown) => recoverConfigFromJsonRootSuffix(snapshot),
|
||||
}));
|
||||
@@ -186,6 +191,8 @@ describe("gateway run option collisions", () => {
|
||||
resetRuntimeCapture();
|
||||
configState.cfg = {};
|
||||
configState.snapshot = { exists: false };
|
||||
readBestEffortConfig.mockClear();
|
||||
readConfigFileSnapshotWithPluginMetadata.mockClear();
|
||||
controlUiState.root = "/tmp/openclaw-control-ui";
|
||||
gatewayLogMessages.length = 0;
|
||||
recoverConfigFromLastKnownGood.mockReset();
|
||||
@@ -306,10 +313,15 @@ describe("gateway run option collisions", () => {
|
||||
it("starts gateway when token mode has no configured token (startup bootstrap path)", async () => {
|
||||
await runGatewayCli(["gateway", "run", "--allow-unconfigured"]);
|
||||
|
||||
expect(readConfigFileSnapshotWithPluginMetadata).toHaveBeenCalledTimes(1);
|
||||
expect(readBestEffortConfig).not.toHaveBeenCalled();
|
||||
expect(startGatewayServer).toHaveBeenCalledWith(
|
||||
18789,
|
||||
expect.objectContaining({
|
||||
bind: "loopback",
|
||||
startupConfigSnapshotRead: {
|
||||
snapshot: configState.snapshot,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -339,7 +351,7 @@ describe("gateway run option collisions", () => {
|
||||
expect(writeDiagnosticStabilityBundleForFailureSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("blocks startup when the observed snapshot loses gateway.mode even if loadConfig still says local", async () => {
|
||||
it("blocks startup when the observed snapshot loses gateway.mode", async () => {
|
||||
configState.cfg = {
|
||||
gateway: {
|
||||
mode: "local",
|
||||
@@ -365,6 +377,7 @@ describe("gateway run option collisions", () => {
|
||||
`Config write audit: ${path.join("/tmp", "logs", "config-audit.jsonl")}`,
|
||||
);
|
||||
expect(startGatewayServer).not.toHaveBeenCalled();
|
||||
expect(readBestEffortConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("restores last-known-good config before startup when the effective config is invalid", async () => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
GatewayAuthMode,
|
||||
GatewayBindMode,
|
||||
GatewayTailscaleMode,
|
||||
ReadConfigFileSnapshotWithPluginMetadataResult,
|
||||
} from "../../config/config.js";
|
||||
import { formatConfigIssueSummary } from "../../config/issue-format.js";
|
||||
import { CONFIG_PATH, resolveGatewayPort, resolveStateDir } from "../../config/paths.js";
|
||||
@@ -271,18 +272,21 @@ function getGatewayStartGuardErrors(params: {
|
||||
|
||||
async function readGatewayStartupConfig(params: {
|
||||
startupTrace: ReturnType<typeof createGatewayCliStartupTrace>;
|
||||
}): Promise<{ cfg: OpenClawConfig; snapshot: ConfigFileSnapshot | null }> {
|
||||
}): Promise<{
|
||||
cfg: OpenClawConfig;
|
||||
snapshot: ConfigFileSnapshot | null;
|
||||
startupConfigSnapshotRead?: ReadConfigFileSnapshotWithPluginMetadataResult;
|
||||
}> {
|
||||
const {
|
||||
readBestEffortConfig,
|
||||
readConfigFileSnapshot,
|
||||
readConfigFileSnapshotWithPluginMetadata,
|
||||
recoverConfigFromLastKnownGood,
|
||||
recoverConfigFromJsonRootSuffix,
|
||||
} = await import("../../config/config.js");
|
||||
let cfg = await params.startupTrace.measure("cli.config-load", () => readBestEffortConfig());
|
||||
let snapshot: ConfigFileSnapshot | null = await params.startupTrace.measure(
|
||||
"cli.config-snapshot",
|
||||
() => readConfigFileSnapshot().catch(() => null),
|
||||
);
|
||||
let snapshotRead: ReadConfigFileSnapshotWithPluginMetadataResult | null =
|
||||
await params.startupTrace.measure("cli.config-snapshot", () =>
|
||||
readConfigFileSnapshotWithPluginMetadata().catch(() => null),
|
||||
);
|
||||
let snapshot: ConfigFileSnapshot | null = snapshotRead?.snapshot ?? null;
|
||||
if (snapshot?.exists && !snapshot.valid) {
|
||||
const invalidSnapshot = snapshot;
|
||||
const recovered = await params.startupTrace.measure("cli.config-recovery", () =>
|
||||
@@ -317,9 +321,10 @@ async function readGatewayStartupConfig(params: {
|
||||
`gateway: failed to persist config auto-recovery notice: ${formatErrorMessage(err)}`,
|
||||
);
|
||||
}
|
||||
snapshot = await params.startupTrace.measure("cli.config-snapshot-reload", () =>
|
||||
readConfigFileSnapshot().catch(() => null),
|
||||
snapshotRead = await params.startupTrace.measure("cli.config-snapshot-reload", () =>
|
||||
readConfigFileSnapshotWithPluginMetadata().catch(() => null),
|
||||
);
|
||||
snapshot = snapshotRead?.snapshot ?? null;
|
||||
} else {
|
||||
const repaired = await params.startupTrace.measure("cli.config-prefix-recovery", () =>
|
||||
recoverConfigFromJsonRootSuffix(invalidSnapshot),
|
||||
@@ -328,16 +333,19 @@ async function readGatewayStartupConfig(params: {
|
||||
gatewayLog.warn(
|
||||
`gateway: repaired invalid effective config by stripping a non-JSON prefix: ${invalidSnapshot.path}`,
|
||||
);
|
||||
snapshot = await params.startupTrace.measure("cli.config-snapshot-reload", () =>
|
||||
readConfigFileSnapshot().catch(() => null),
|
||||
snapshotRead = await params.startupTrace.measure("cli.config-snapshot-reload", () =>
|
||||
readConfigFileSnapshotWithPluginMetadata().catch(() => null),
|
||||
);
|
||||
snapshot = snapshotRead?.snapshot ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (snapshot?.valid) {
|
||||
cfg = snapshot.config;
|
||||
}
|
||||
return { cfg, snapshot };
|
||||
const cfg = snapshot?.config ?? {};
|
||||
return {
|
||||
cfg,
|
||||
snapshot,
|
||||
...(snapshotRead ? { startupConfigSnapshotRead: snapshotRead } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function resolveGatewayRunOptions(opts: GatewayRunOpts, command?: Command): GatewayRunOpts {
|
||||
@@ -563,7 +571,9 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
|
||||
}
|
||||
|
||||
gatewayLog.info("loading configuration…");
|
||||
const { cfg, snapshot } = await readGatewayStartupConfig({ startupTrace });
|
||||
const { cfg, snapshot, startupConfigSnapshotRead } = await readGatewayStartupConfig({
|
||||
startupTrace,
|
||||
});
|
||||
void maybeLogPendingControlUiBuild(cfg).catch((err) => {
|
||||
gatewayLog.warn(`Control UI asset check failed: ${String(err)}`);
|
||||
});
|
||||
@@ -842,6 +852,7 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
|
||||
auth: authOverride,
|
||||
tailscale: tailscaleOverride,
|
||||
startupStartedAt,
|
||||
...(startupConfigSnapshotRead ? { startupConfigSnapshotRead } : {}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user