mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:10:44 +00:00
fix: allow doctor repair size drops
This commit is contained in:
@@ -215,6 +215,11 @@ export type ConfigWriteOptions = {
|
||||
* Normal writers must keep this false so clobbers are rejected before disk commit.
|
||||
*/
|
||||
allowDestructiveWrite?: boolean;
|
||||
/**
|
||||
* Allow an intentional large config size drop while keeping other destructive
|
||||
* guards active. Used by repair flows that remove stale or legacy config.
|
||||
*/
|
||||
allowConfigSizeDrop?: boolean;
|
||||
/**
|
||||
* Suppress human-readable output logs (overwrite/anomaly messages).
|
||||
* Useful when the caller wants machine-readable output only (--json mode).
|
||||
@@ -399,9 +404,14 @@ function resolveConfigWriteSuspiciousReasons(params: {
|
||||
return reasons;
|
||||
}
|
||||
|
||||
function resolveConfigWriteBlockingReasons(suspicious: string[]): string[] {
|
||||
function resolveConfigWriteBlockingReasons(
|
||||
suspicious: string[],
|
||||
options: Pick<ConfigWriteOptions, "allowConfigSizeDrop"> = {},
|
||||
): string[] {
|
||||
return suspicious.filter(
|
||||
(reason) => reason.startsWith("size-drop:") || reason === "gateway-mode-removed",
|
||||
(reason) =>
|
||||
(reason.startsWith("size-drop:") && options.allowConfigSizeDrop !== true) ||
|
||||
reason === "gateway-mode-removed",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2165,7 +2175,7 @@ export function createConfigIO(
|
||||
}),
|
||||
});
|
||||
};
|
||||
const blockingReasons = resolveConfigWriteBlockingReasons(suspiciousReasons);
|
||||
const blockingReasons = resolveConfigWriteBlockingReasons(suspiciousReasons, options);
|
||||
if (blockingReasons.length > 0 && options.allowDestructiveWrite !== true) {
|
||||
const rejectedPath = `${configPath}.rejected.${formatConfigArtifactTimestamp(new Date().toISOString())}`;
|
||||
await deps.fs.promises
|
||||
@@ -2426,6 +2436,7 @@ export async function writeConfigFile(
|
||||
}),
|
||||
unsetPaths: resolveManagedUnsetPathsForWrite(options.unsetPaths),
|
||||
allowDestructiveWrite: options.allowDestructiveWrite,
|
||||
allowConfigSizeDrop: options.allowConfigSizeDrop,
|
||||
skipRuntimeSnapshotRefresh: options.skipRuntimeSnapshotRefresh,
|
||||
skipOutputLogs: options.skipOutputLogs,
|
||||
});
|
||||
|
||||
@@ -602,6 +602,68 @@ describe("config io write", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("allows intentional size-drop writes without disabling gateway-mode protection", async () => {
|
||||
await withSuiteHome(async (home) => {
|
||||
const configPath = path.join(home, ".openclaw", "openclaw.json");
|
||||
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
||||
const original = {
|
||||
meta: { lastTouchedVersion: "2026.4.30" },
|
||||
gateway: { mode: "local" },
|
||||
channels: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
allowFrom: Array.from({ length: 80 }, (_, index) => `telegram:${index}`),
|
||||
},
|
||||
},
|
||||
} satisfies ConfigFileSnapshot["config"];
|
||||
const originalRaw = `${JSON.stringify(original, null, 2)}\n`;
|
||||
await fs.writeFile(configPath, originalRaw, "utf-8");
|
||||
const io = createConfigIO({
|
||||
env: { VITEST: "true" } as NodeJS.ProcessEnv,
|
||||
homedir: () => home,
|
||||
logger: silentLogger,
|
||||
});
|
||||
const baseSnapshot = {
|
||||
path: configPath,
|
||||
exists: true,
|
||||
raw: originalRaw,
|
||||
parsed: original,
|
||||
sourceConfig: original,
|
||||
resolved: original,
|
||||
valid: true,
|
||||
runtimeConfig: original,
|
||||
config: original,
|
||||
issues: [],
|
||||
warnings: [],
|
||||
legacyIssues: [],
|
||||
} satisfies ConfigFileSnapshot;
|
||||
|
||||
await expect(
|
||||
io.writeConfigFile(
|
||||
{ meta: original.meta, gateway: { mode: "local" } },
|
||||
{
|
||||
allowConfigSizeDrop: true,
|
||||
baseSnapshot,
|
||||
},
|
||||
),
|
||||
).resolves.toMatchObject({
|
||||
persistedConfig: expect.objectContaining({ gateway: { mode: "local" } }),
|
||||
});
|
||||
|
||||
await expect(
|
||||
io.writeConfigFile(
|
||||
{ meta: original.meta },
|
||||
{
|
||||
allowConfigSizeDrop: true,
|
||||
baseSnapshot,
|
||||
},
|
||||
),
|
||||
).rejects.toMatchObject({
|
||||
code: "CONFIG_WRITE_REJECTED",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps authored agent provider params during narrowed internal agent writes", async () => {
|
||||
await withSuiteHome(async (home) => {
|
||||
const configPath = path.join(home, ".openclaw", "openclaw.json");
|
||||
|
||||
@@ -526,6 +526,9 @@ async function runWriteConfigHealth(ctx: DoctorHealthFlowContext): Promise<void>
|
||||
await replaceConfigFile({
|
||||
nextConfig: ctx.cfg,
|
||||
afterWrite: { mode: "auto" },
|
||||
writeOptions: {
|
||||
allowConfigSizeDrop: ctx.configResult.shouldWriteConfig === true,
|
||||
},
|
||||
});
|
||||
logConfigUpdated(ctx.runtime);
|
||||
const backupPath = `${CONFIG_PATH}.bak`;
|
||||
|
||||
Reference in New Issue
Block a user