mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:20:44 +00:00
fix: block config writes when plugin install migration fails
This commit is contained in:
@@ -1240,11 +1240,54 @@ export function createConfigIO(
|
||||
err,
|
||||
)}`,
|
||||
);
|
||||
return configRaw;
|
||||
}
|
||||
|
||||
return stripped;
|
||||
}
|
||||
|
||||
function ensureShippedPluginInstallConfigRecordsMigratedForWrite(
|
||||
snapshot: ConfigFileSnapshot,
|
||||
): void {
|
||||
const installRecords = {
|
||||
...extractShippedPluginInstallConfigRecords(snapshot.sourceConfig),
|
||||
...extractShippedPluginInstallConfigRecords(snapshot.parsed),
|
||||
};
|
||||
if (Object.keys(installRecords).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stateDir = resolveStateDir(deps.env, deps.homedir);
|
||||
const existingRecords = loadInstalledPluginIndexInstallRecordsSync({
|
||||
env: deps.env,
|
||||
stateDir,
|
||||
});
|
||||
if (Object.keys(installRecords).every((pluginId) => pluginId in existingRecords)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
writePersistedInstalledPluginIndexInstallRecordsSync(
|
||||
{
|
||||
...installRecords,
|
||||
...existingRecords,
|
||||
},
|
||||
{
|
||||
config: coerceConfig(stripShippedPluginInstallConfigRecords(snapshot.sourceConfig)),
|
||||
env: deps.env,
|
||||
stateDir,
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Config write blocked: shipped plugins.installs records in ${configPath} could not be migrated into the plugin index. Fix state directory permissions or run openclaw plugins registry --refresh, then retry. ${formatErrorMessage(
|
||||
err,
|
||||
)}`,
|
||||
{ cause: err },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function loadConfig(): OpenClawConfig {
|
||||
try {
|
||||
maybeLoadDotEnvForConfig(deps.env);
|
||||
@@ -1677,6 +1720,7 @@ export function createConfigIO(
|
||||
const unsetPaths = resolveManagedUnsetPathsForWrite(options.unsetPaths);
|
||||
let persistCandidate: unknown = cfg;
|
||||
const snapshot = options.baseSnapshot ?? (await readConfigFileSnapshotInternal()).snapshot;
|
||||
ensureShippedPluginInstallConfigRecordsMigratedForWrite(snapshot);
|
||||
let envRefMap: Map<string, string> | null = null;
|
||||
let changedPaths: Set<string> | null = null;
|
||||
if (snapshot.valid && snapshot.exists) {
|
||||
|
||||
@@ -185,6 +185,51 @@ describe("config io write", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps shipped plugin install config records when index migration fails", async () => {
|
||||
await withSuiteHome(async (home) => {
|
||||
const configPath = path.join(home, ".openclaw", "openclaw.json");
|
||||
const unwritableStatePath = path.join(home, ".openclaw");
|
||||
const pluginDir = path.join(unwritableStatePath, "plugins", "demo");
|
||||
const original = {
|
||||
plugins: {
|
||||
entries: { demo: { enabled: true } },
|
||||
installs: {
|
||||
demo: {
|
||||
source: "npm",
|
||||
spec: "demo@1.0.0",
|
||||
installPath: pluginDir,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
||||
await fs.writeFile(configPath, `${JSON.stringify(original, null, 2)}\n`, "utf-8");
|
||||
const warn = vi.fn();
|
||||
const io = createConfigIO({
|
||||
env: { OPENCLAW_TEST_FAST: "1" } as NodeJS.ProcessEnv,
|
||||
homedir: () => home,
|
||||
logger: { warn, error: vi.fn() },
|
||||
});
|
||||
await fs.writeFile(path.join(unwritableStatePath, "plugins"), "not a directory", "utf-8");
|
||||
|
||||
expect(() => io.loadConfig()).toThrow('Unrecognized key: "installs"');
|
||||
expect(warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining("could not migrate shipped plugins.installs records"),
|
||||
);
|
||||
|
||||
await expect(io.writeConfigFile({ gateway: { mode: "local" } })).rejects.toThrow(
|
||||
"Config write blocked: shipped plugins.installs records",
|
||||
);
|
||||
|
||||
const persisted = JSON.parse(await fs.readFile(configPath, "utf-8")) as typeof original;
|
||||
expect(persisted.plugins.installs.demo).toMatchObject({
|
||||
source: "npm",
|
||||
spec: "demo@1.0.0",
|
||||
installPath: pluginDir,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const writeGatewayPortAndReadConfig = async (home: string, configPath: string) => {
|
||||
const io = createFastConfigIO(home);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user