fix(update): defer legacy parent plugin repair

This commit is contained in:
Peter Steinberger
2026-05-20 13:43:17 +01:00
parent 29faac2f9c
commit 93c2d1ea99
4 changed files with 23 additions and 17 deletions
@@ -787,10 +787,13 @@ async function installCandidate(params: {
const existingNpmPackagePath = npmInstallSpec
? resolveExistingCandidateNpmPackagePath({ candidate, npmDir })
: null;
const existingNpmPackageVersion = existingNpmPackagePath
? await readNpmPackageVersion(existingNpmPackagePath)
: undefined;
if (
existingNpmPackagePath &&
existingNpmPackageVersion &&
npmInstallSpec &&
clawhubInstallSpec &&
params.mode !== "update" &&
isPostCoreConvergencePass(params.env)
) {
@@ -800,6 +803,7 @@ async function installCandidate(params: {
npmInstallSpec,
npmRecordSpec: npmSpecs?.recordSpec ?? npmInstallSpec,
packagePath: existingNpmPackagePath,
version: existingNpmPackageVersion,
});
}
const shouldTryClawHub =
@@ -965,12 +969,12 @@ async function adoptExistingNpmPackage(params: {
npmInstallSpec: string;
npmRecordSpec: string;
packagePath: string;
version: string;
}): Promise<{
records: Record<string, PluginInstallRecord>;
changes: string[];
warnings: string[];
}> {
const version = await readNpmPackageVersion(params.packagePath);
const npmName = parseRegistryNpmSpec(params.npmInstallSpec)?.name;
return {
records: {
@@ -980,9 +984,10 @@ async function adoptExistingNpmPackage(params: {
spec: params.npmRecordSpec,
installPath: params.packagePath,
installedAt: new Date().toISOString(),
...(version ? { version, resolvedVersion: version } : {}),
version: params.version,
resolvedVersion: params.version,
...(npmName ? { resolvedName: npmName } : {}),
...(npmName && version ? { resolvedSpec: `${npmName}@${version}` } : {}),
...(npmName ? { resolvedSpec: `${npmName}@${params.version}` } : {}),
},
},
changes: [
@@ -428,7 +428,7 @@ describe("configured plugin install release step", () => {
});
});
it("repairs package-manager plugins for writable legacy parents without explicit deferral", async () => {
it("defers package-manager plugin release completion for writable legacy parents", async () => {
mocks.repairMissingPluginInstallsForIds.mockResolvedValue({
changes: ['Installed missing configured plugin "discord".'],
warnings: [],
@@ -459,8 +459,8 @@ describe("configured plugin install release step", () => {
expect(result).toEqual({
changes: ['Installed missing configured plugin "discord".'],
warnings: [],
completed: true,
touchedConfig: true,
completed: false,
touchedConfig: false,
});
});
@@ -36,7 +36,7 @@ describe("update-phase env helpers", () => {
expect(isPostCoreConvergencePass(env)).toBe(true);
});
it("defers configured plugin repair only for explicit update handoffs", () => {
it("defers configured plugin repair for post-core handoffs", () => {
expect(
shouldDeferConfiguredPluginInstallRepair({
[UPDATE_IN_PROGRESS_ENV]: "1",
@@ -53,7 +53,7 @@ describe("update-phase env helpers", () => {
[UPDATE_IN_PROGRESS_ENV]: "1",
[UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV]: "1",
}),
).toBe(false);
).toBe(true);
expect(
shouldDeferConfiguredPluginInstallRepair({
[UPDATE_IN_PROGRESS_ENV]: "1",
@@ -80,7 +80,7 @@ describe("update-phase env helpers", () => {
[UPDATE_IN_PROGRESS_ENV]: "1",
[UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV]: "1",
}),
).toBe(true);
).toBe(false);
expect(
isLegacyPackageUpdateDoctorPass({
[UPDATE_IN_PROGRESS_ENV]: "1",
+8 -7
View File
@@ -40,14 +40,15 @@ export function isUpdatePackageSwapInProgress(env: NodeJS.ProcessEnv): boolean {
/**
* True iff configured plugin install repair should be deferred because the
* updater guarantees a later post-core convergence pass. Older shipped
* parents may set only the writable-config marker; they still resume after
* the candidate doctor exits, so the candidate must repair payloads before
* control returns to that stale process.
* parents may set only the writable-config marker. Those parents still have a
* post-core handoff, but their in-memory install records are stale after the
* candidate doctor exits, so defer payload repair to the updated child process.
*/
export function shouldDeferConfiguredPluginInstallRepair(env: NodeJS.ProcessEnv): boolean {
return (
isUpdatePackageSwapInProgress(env) &&
isTruthyEnvValue(env[UPDATE_DEFER_CONFIGURED_PLUGIN_INSTALL_REPAIR_ENV])
(isTruthyEnvValue(env[UPDATE_DEFER_CONFIGURED_PLUGIN_INSTALL_REPAIR_ENV]) ||
isTruthyEnvValue(env[UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV]))
);
}
@@ -67,9 +68,9 @@ export function isLegacyParentWritableUpdateDoctorPass(env: NodeJS.ProcessEnv):
}
/**
* True iff this newer doctor is running under an older updater. Legacy
* updaters set only `OPENCLAW_UPDATE_IN_PROGRESS`; they do not opt into the
* post-core convergence pass, so configured plugin repair must happen now.
* True iff this newer doctor is running under an older updater that does not
* advertise any post-core handoff marker. Those parents set only
* `OPENCLAW_UPDATE_IN_PROGRESS`, so configured plugin repair must happen now.
*/
export function isLegacyPackageUpdateDoctorPass(env: NodeJS.ProcessEnv): boolean {
return isUpdatePackageSwapInProgress(env) && !shouldDeferConfiguredPluginInstallRepair(env);