From d3519ce42ce196d37d971de9b2c5e7cb4a3629c0 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 2 May 2026 16:39:39 +0100 Subject: [PATCH] fix: keep configured plugin repair scoped --- scripts/e2e/lib/upgrade-survivor/run.sh | 6 ++ src/flows/doctor-health-contributions.test.ts | 76 ++++++++++++++++++- src/flows/doctor-health-contributions.ts | 3 + 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/scripts/e2e/lib/upgrade-survivor/run.sh b/scripts/e2e/lib/upgrade-survivor/run.sh index 415caa57120..8a7328641fe 100644 --- a/scripts/e2e/lib/upgrade-survivor/run.sh +++ b/scripts/e2e/lib/upgrade-survivor/run.sh @@ -41,6 +41,7 @@ FAILURE_PHASE="" FAILURE_MESSAGE="" gateway_pid="" clawhub_fixture_pid="" +configured_plugin_installs_clawhub_fixture_owned="" baseline_spec="" baseline_version="" baseline_version_expected="0" @@ -288,6 +289,7 @@ configured_plugin_installs_enabled() { start_configured_plugin_installs_clawhub_fixture() { configured_plugin_installs_enabled || return 0 + configured_plugin_installs_clawhub_fixture_owned="" if [ -n "${OPENCLAW_CLAWHUB_URL:-}" ] || [ -n "${CLAWHUB_URL:-}" ]; then return 0 fi @@ -318,6 +320,7 @@ NODE for _ in $(seq 1 100); do if [ -s "$port_file" ]; then export OPENCLAW_CLAWHUB_URL="http://127.0.0.1:$(cat "$port_file")" + configured_plugin_installs_clawhub_fixture_owned="1" echo "Configured plugin install scenario using ClawHub 404 fixture: $OPENCLAW_CLAWHUB_URL" return 0 fi @@ -329,6 +332,9 @@ NODE assert_configured_plugin_installs_clawhub_attempted() { configured_plugin_installs_enabled || return 0 + if [ "${configured_plugin_installs_clawhub_fixture_owned:-}" != "1" ]; then + return 0 + fi local requests_file="$ARTIFACT_ROOT/clawhub-not-found-requests.jsonl" if ! grep -q '/api/v1/packages/%40openclaw%2Fmatrix' "$requests_file" 2>/dev/null; then echo "configured plugin install scenario did not attempt ClawHub for @openclaw/matrix" >&2 diff --git a/src/flows/doctor-health-contributions.test.ts b/src/flows/doctor-health-contributions.test.ts index 81315b7276c..eb3e8f6b9fb 100644 --- a/src/flows/doctor-health-contributions.test.ts +++ b/src/flows/doctor-health-contributions.test.ts @@ -1,10 +1,32 @@ -import { describe, expect, it } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import { resolveDoctorHealthContributions, shouldSkipLegacyUpdateDoctorConfigWrite, } from "./doctor-health-contributions.js"; +const mocks = vi.hoisted(() => ({ + maybeRunConfiguredPluginInstallReleaseStep: vi.fn(), + note: vi.fn(), +})); + +vi.mock("../commands/doctor/shared/release-configured-plugin-installs.js", () => ({ + maybeRunConfiguredPluginInstallReleaseStep: mocks.maybeRunConfiguredPluginInstallReleaseStep, +})); + +vi.mock("../terminal/note.js", () => ({ + note: mocks.note, +})); + +vi.mock("../version.js", () => ({ + VERSION: "2026.5.2-test", +})); + describe("doctor health contributions", () => { + beforeEach(() => { + mocks.maybeRunConfiguredPluginInstallReleaseStep.mockReset(); + mocks.note.mockReset(); + }); + it("runs release configured plugin install repair before plugin registry and final config writes", () => { const ids = resolveDoctorHealthContributions().map((entry) => entry.id); @@ -15,6 +37,58 @@ describe("doctor health contributions", () => { ); expect(ids.indexOf("doctor:plugin-registry")).toBeLessThan(ids.indexOf("doctor:write-config")); }); + + it("keeps release configured plugin installs repair-only", async () => { + const contribution = resolveDoctorHealthContributions().find( + (entry) => entry.id === "doctor:release-configured-plugin-installs", + ); + expect(contribution).toBeDefined(); + const ctx = { + cfg: {}, + configResult: { cfg: {}, sourceLastTouchedVersion: "2026.4.29" }, + sourceConfigValid: true, + prompter: { shouldRepair: false }, + env: {}, + } as Parameters["run"]>[0]; + + await contribution?.run(ctx); + + expect(mocks.maybeRunConfiguredPluginInstallReleaseStep).not.toHaveBeenCalled(); + expect(mocks.note).not.toHaveBeenCalled(); + }); + + it("stamps release configured plugin installs after repair changes", async () => { + mocks.maybeRunConfiguredPluginInstallReleaseStep.mockResolvedValue({ + changes: ["Installed configured plugin matrix."], + warnings: [], + touchedConfig: true, + }); + const contribution = resolveDoctorHealthContributions().find( + (entry) => entry.id === "doctor:release-configured-plugin-installs", + ); + expect(contribution).toBeDefined(); + const ctx = { + cfg: {}, + configResult: { cfg: {}, sourceLastTouchedVersion: "2026.4.29" }, + sourceConfigValid: true, + prompter: { shouldRepair: true }, + env: {}, + } as Parameters["run"]>[0]; + + await contribution?.run(ctx); + + expect(mocks.maybeRunConfiguredPluginInstallReleaseStep).toHaveBeenCalledWith({ + cfg: {}, + env: {}, + touchedVersion: "2026.4.29", + }); + expect(mocks.note).toHaveBeenCalledWith( + "Installed configured plugin matrix.", + "Doctor changes", + ); + expect(ctx.cfg.meta?.lastTouchedVersion).toBe("2026.5.2-test"); + }); + it("checks command owner configuration before final config writes", () => { const ids = resolveDoctorHealthContributions().map((entry) => entry.id); diff --git a/src/flows/doctor-health-contributions.ts b/src/flows/doctor-health-contributions.ts index d36830b0df3..36b9f2bbf9f 100644 --- a/src/flows/doctor-health-contributions.ts +++ b/src/flows/doctor-health-contributions.ts @@ -275,6 +275,9 @@ async function runReleaseConfiguredPluginInstallsHealth( if (!ctx.sourceConfigValid) { return; } + if (!ctx.prompter.shouldRepair) { + return; + } const { maybeRunConfiguredPluginInstallReleaseStep } = await import("../commands/doctor/shared/release-configured-plugin-installs.js"); const { note } = await import("../terminal/note.js");