mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix: preserve runtime snapshot during refresh
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
clearRuntimeConfigSnapshot,
|
||||
getRuntimeConfigSourceSnapshot,
|
||||
loadConfig,
|
||||
setRuntimeConfigSnapshotRefreshHandler,
|
||||
setRuntimeConfigSnapshot,
|
||||
writeConfigFile,
|
||||
} from "./io.js";
|
||||
@@ -41,6 +42,7 @@ function createRuntimeConfig(): OpenClawConfig {
|
||||
}
|
||||
|
||||
function resetRuntimeConfigState(): void {
|
||||
setRuntimeConfigSnapshotRefreshHandler(null);
|
||||
clearRuntimeConfigSnapshot();
|
||||
clearConfigCache();
|
||||
}
|
||||
@@ -169,4 +171,44 @@ describe("runtime config snapshot writes", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps the last-known-good runtime snapshot active while a specialized refresh is pending", async () => {
|
||||
await withTempHome("openclaw-config-runtime-refresh-pending-", async (home) => {
|
||||
const configPath = path.join(home, ".openclaw", "openclaw.json");
|
||||
const sourceConfig = createSourceConfig();
|
||||
const runtimeConfig = createRuntimeConfig();
|
||||
const nextRuntimeConfig: OpenClawConfig = {
|
||||
...runtimeConfig,
|
||||
gateway: { auth: { mode: "token" as const } },
|
||||
};
|
||||
|
||||
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
||||
await fs.writeFile(configPath, `${JSON.stringify(sourceConfig, null, 2)}\n`, "utf8");
|
||||
|
||||
let releaseRefresh!: () => void;
|
||||
const refreshPending = new Promise<boolean>((resolve) => {
|
||||
releaseRefresh = () => resolve(true);
|
||||
});
|
||||
|
||||
try {
|
||||
setRuntimeConfigSnapshot(runtimeConfig, sourceConfig);
|
||||
setRuntimeConfigSnapshotRefreshHandler({
|
||||
refresh: async ({ sourceConfig: refreshedSource }) => {
|
||||
expect(refreshedSource.gateway?.auth).toEqual({ mode: "token" });
|
||||
expect(loadConfig().gateway?.auth).toBeUndefined();
|
||||
return await refreshPending;
|
||||
},
|
||||
});
|
||||
|
||||
const writePromise = writeConfigFile(nextRuntimeConfig);
|
||||
await Promise.resolve();
|
||||
|
||||
expect(loadConfig().gateway?.auth).toBeUndefined();
|
||||
releaseRefresh();
|
||||
await writePromise;
|
||||
} finally {
|
||||
resetRuntimeConfigState();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1425,6 +1425,7 @@ export async function writeConfigFile(
|
||||
): Promise<void> {
|
||||
const io = createConfigIO();
|
||||
let nextCfg = cfg;
|
||||
const hadRuntimeSnapshot = Boolean(runtimeConfigSnapshot);
|
||||
const hadBothSnapshots = Boolean(runtimeConfigSnapshot && runtimeConfigSourceSnapshot);
|
||||
if (hadBothSnapshots) {
|
||||
const runtimePatch = createMergePatch(runtimeConfigSnapshot!, cfg);
|
||||
@@ -1436,9 +1437,8 @@ export async function writeConfigFile(
|
||||
envSnapshotForRestore: sameConfigPath ? options.envSnapshotForRestore : undefined,
|
||||
unsetPaths: options.unsetPaths,
|
||||
});
|
||||
// Keep follow-up loadConfig() in sync with the just-persisted config (fixes race where a
|
||||
// second connection's agents.update fails with "agent not found" after agents.create).
|
||||
clearRuntimeConfigSnapshot();
|
||||
// Keep the last-known-good runtime snapshot active until the specialized refresh path
|
||||
// succeeds, so concurrent readers do not observe unresolved SecretRefs mid-refresh.
|
||||
const refreshHandler = runtimeConfigSnapshotRefreshHandler;
|
||||
if (refreshHandler) {
|
||||
try {
|
||||
@@ -1460,11 +1460,15 @@ export async function writeConfigFile(
|
||||
}
|
||||
}
|
||||
if (hadBothSnapshots) {
|
||||
// Refresh both snapshots from disk so follow-up reads get normalized config and
|
||||
// Refresh both snapshots from disk atomically so follow-up reads get normalized config and
|
||||
// subsequent writes still get secret-preservation merge-patch (hadBothSnapshots stays true).
|
||||
const fresh = loadConfig();
|
||||
const fresh = io.loadConfig();
|
||||
setRuntimeConfigSnapshot(fresh, nextCfg);
|
||||
return;
|
||||
}
|
||||
// When we had no snapshot (or only one), do not set a new one: leave callers reading from
|
||||
// disk/cache so external/manual edits to openclaw.json remain visible (no stale snapshot).
|
||||
if (hadRuntimeSnapshot) {
|
||||
clearRuntimeConfigSnapshot();
|
||||
}
|
||||
// When we had no runtime snapshot, keep callers reading from disk/cache so external/manual
|
||||
// edits to openclaw.json remain visible (no stale snapshot).
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user