From fb5409eac2f9da032d6574dda913c973e9e2011f Mon Sep 17 00:00:00 2001 From: Frank Yang Date: Sun, 22 Feb 2026 20:01:20 -0800 Subject: [PATCH] fix(config): avoid mutating caller config when unsetting paths --- src/config/io.ts | 5 ++++- src/config/io.write-config.test.ts | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/config/io.ts b/src/config/io.ts index fba3be8d63f..2fa4eb6a2a6 100644 --- a/src/config/io.ts +++ b/src/config/io.ts @@ -1011,10 +1011,13 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) { const dir = path.dirname(configPath); await deps.fs.promises.mkdir(dir, { recursive: true, mode: 0o700 }); - const outputConfig = + const outputConfigBase = envRefMap && changedPaths ? (restoreEnvRefsFromMap(cfgToWrite, "", envRefMap, changedPaths) as OpenClawConfig) : cfgToWrite; + const outputConfig = options.unsetPaths?.length + ? cloneUnknown(outputConfigBase) + : outputConfigBase; if (options.unsetPaths?.length) { for (const unsetPath of options.unsetPaths) { if (!Array.isArray(unsetPath) || unsetPath.length === 0) { diff --git a/src/config/io.write-config.test.ts b/src/config/io.write-config.test.ts index 110d81ef61e..cacf96ea306 100644 --- a/src/config/io.write-config.test.ts +++ b/src/config/io.write-config.test.ts @@ -124,6 +124,30 @@ describe("config io write", () => { }); }); + it("does not mutate caller config when unsetPaths is applied on first write", async () => { + await withTempHome("openclaw-config-io-", async (home) => { + const configPath = path.join(home, ".openclaw", "openclaw.json"); + const io = createConfigIO({ + env: {} as NodeJS.ProcessEnv, + homedir: () => home, + logger: silentLogger, + }); + + const input: Record = { + gateway: { mode: "local" }, + commands: { ownerDisplay: "hash" }, + }; + + await io.writeConfigFile(input, { unsetPaths: [["commands", "ownerDisplay"]] }); + + expect((input.commands as Record).ownerDisplay).toBe("hash"); + const persisted = JSON.parse(await fs.readFile(configPath, "utf-8")) as { + commands?: Record; + }; + expect(persisted.commands ?? {}).not.toHaveProperty("ownerDisplay"); + }); + }); + it("preserves env var references when writing", async () => { await withTempHome("openclaw-config-io-", async (home) => { const { configPath, io, snapshot } = await writeConfigAndCreateIo({