Skip to content

Commit 4b24ce9

Browse files
authored
fix(controller): seed config writer cache from disk on cold start (#349)
On process restart, lastWrittenContent is null, causing the first write() to always hit disk even if the config file already has identical content. This triggers an unnecessary OpenClaw reload on every controller restart. Read the existing file to seed the in-memory cache on the first write() call, so cold starts skip the write when content matches.
1 parent 1decf95 commit 4b24ce9

2 files changed

Lines changed: 45 additions & 4 deletions

File tree

apps/controller/src/runtime/openclaw-config-writer.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mkdir, stat, writeFile } from "node:fs/promises";
1+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
22
import path from "node:path";
33
import type { OpenClawConfig } from "@nexu/shared";
44
import type { ControllerEnv } from "../app/env.js";
@@ -14,6 +14,20 @@ export class OpenClawConfigWriter {
1414
await mkdir(path.dirname(this.env.openclawConfigPath), { recursive: true });
1515
const content = `${JSON.stringify(config, null, 2)}\n`;
1616

17+
// On cold start, seed the cache from the existing file on disk so the
18+
// first write() after a process restart doesn't trigger an unnecessary
19+
// OpenClaw reload when the config hasn't actually changed.
20+
if (this.lastWrittenContent === null) {
21+
try {
22+
this.lastWrittenContent = await readFile(
23+
this.env.openclawConfigPath,
24+
"utf8",
25+
);
26+
} catch {
27+
// File doesn't exist yet — leave cache empty.
28+
}
29+
}
30+
1731
// Skip writing if the content hasn't changed since the last write.
1832
// This prevents OpenClaw's file watcher from triggering unnecessary
1933
// reloads/restarts when syncAll() is called without actual config changes

apps/controller/tests/openclaw-config-writer.test.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ describe("OpenClawConfigWriter", () => {
109109
expect(finalStat.mtimeMs).toBe(firstStat.mtimeMs);
110110
});
111111

112-
it("separate writer instances do not share state", async () => {
112+
it("new writer instance seeds cache from existing file on cold start", async () => {
113113
const config = makeConfig();
114114

115115
const writer1 = new OpenClawConfigWriter(env);
@@ -118,11 +118,38 @@ describe("OpenClawConfigWriter", () => {
118118

119119
await new Promise((r) => setTimeout(r, 50));
120120

121-
// A new writer instance has no memory of previous writes
121+
// A new writer instance reads the existing file to seed its cache,
122+
// so it skips the write when content matches (cold-start optimization).
122123
const writer2 = new OpenClawConfigWriter(env);
123124
await writer2.write(config);
124125
const secondStat = await stat(env.openclawConfigPath);
125126

126-
expect(secondStat.mtimeMs).not.toBe(firstStat.mtimeMs);
127+
expect(secondStat.mtimeMs).toBe(firstStat.mtimeMs);
128+
});
129+
130+
it("new writer instance writes when content differs from existing file", async () => {
131+
const configA = makeConfig({ commands: { native: "auto" } });
132+
const configB = makeConfig({ commands: { native: "off" } });
133+
134+
const writer1 = new OpenClawConfigWriter(env);
135+
await writer1.write(configA);
136+
137+
// A new writer reads the existing file, sees different content, and writes.
138+
const writer2 = new OpenClawConfigWriter(env);
139+
await writer2.write(configB);
140+
const written = await readFile(env.openclawConfigPath, "utf8");
141+
142+
expect(JSON.parse(written)).toEqual(configB);
143+
});
144+
145+
it("cold start with no existing file writes normally", async () => {
146+
// No file exists yet — writer should write without error.
147+
const writer = new OpenClawConfigWriter(env);
148+
const config = makeConfig();
149+
150+
await writer.write(config);
151+
152+
const written = await readFile(env.openclawConfigPath, "utf8");
153+
expect(JSON.parse(written)).toEqual(config);
127154
});
128155
});

0 commit comments

Comments
 (0)