fix(update): migrate plugin config before final validation (#122199)

Stage transitional plugin install-record writes without plugin-schema validation, then require fresh doctor and strict validation for every aggregate plugin change.

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Vincent Koc
2026-08-12 03:16:22 +08:00
committed by GitHub
parent 746a188c90
commit e8885eefcd
4 changed files with 77 additions and 7 deletions
@@ -476,11 +476,14 @@ export async function updatePluginsAfterCoreUpdate(params: {
channels: structuredClone(params.restoredAuthoredChannels) as OpenClawConfig["channels"],
};
}
// Installed plugin metadata can own migrations that this process has not loaded yet.
// Finalization runs fresh doctor plus strict validation before the update can complete.
await commitPluginInstallRecordsWithConfig({
previousInstallRecords: pluginInstallRecords,
nextInstallRecords,
nextConfig,
baseHash: params.configSnapshot.hash,
writeOptions: { skipPluginValidation: true },
});
await refreshPluginRegistryAfterConfigMutation({
config: nextConfig,
@@ -288,12 +288,9 @@ export async function finishUpdate(params: {
const completedPluginUpdate = await completePostCorePluginUpdate({
root: postUpdateRoot,
pluginUpdate: initialPluginUpdate,
// A plugin-only update can replace its migration owner without replacing core.
// Downgrades and resume fallbacks can also leave an updated core on disk in this process.
// Aggregate plugin changes and core install changes independently require fresh doctor.
freshDoctorRequired:
didCoreUpdateChangeInstall(params.result) ||
initialPluginUpdate.sync.changed ||
initialPluginUpdate.npm.changed,
didCoreUpdateChangeInstall(params.result) || initialPluginUpdate.changed,
yes: params.opts.yes === true,
json: params.opts.json === true,
timeoutMs: params.updateStepTimeoutMs,
+1 -2
View File
@@ -127,8 +127,7 @@ async function resumePostCoreUpdateUnlocked(params: ResumePostCoreUpdateParams):
const { pluginUpdate } = await completePostCorePluginUpdate({
root: params.root,
pluginUpdate: initialPluginUpdate,
// Only package/channel sync can replace the migration owner loaded by this process.
freshDoctorRequired: initialPluginUpdate.sync.changed || initialPluginUpdate.npm.changed,
freshDoctorRequired: initialPluginUpdate.changed,
yes: params.opts.yes === true,
json: params.opts.json === true,
timeoutMs: params.timeoutMs,