diff --git a/src/commands/doctor/shared/release-configured-plugin-installs.test.ts b/src/commands/doctor/shared/release-configured-plugin-installs.test.ts index a83221168ab..cb157d512aa 100644 --- a/src/commands/doctor/shared/release-configured-plugin-installs.test.ts +++ b/src/commands/doctor/shared/release-configured-plugin-installs.test.ts @@ -311,6 +311,42 @@ describe("configured plugin install release step", () => { }); }); + it("repairs missing configured installs even when a prior update doctor touched config", async () => { + mocks.repairMissingPluginInstallsForIds.mockResolvedValue({ + changes: ['Installed missing configured plugin "discord".'], + warnings: [], + }); + + const { maybeRunConfiguredPluginInstallReleaseStep } = + await import("./release-configured-plugin-installs.js"); + const result = await maybeRunConfiguredPluginInstallReleaseStep({ + cfg: { + plugins: { + entries: { + discord: { enabled: true }, + }, + }, + }, + currentVersion: "2026.5.3-beta.1", + touchedVersion: "2026.5.3-beta.1", + env: {}, + }); + + expect(mocks.repairMissingPluginInstallsForIds).toHaveBeenCalledWith( + expect.objectContaining({ + pluginIds: ["discord"], + channelIds: [], + env: {}, + }), + ); + expect(result).toEqual({ + changes: ['Installed missing configured plugin "discord".'], + warnings: [], + completed: true, + touchedConfig: false, + }); + }); + it("does not touch config when install repair warns", async () => { mocks.detectPluginAutoEnableCandidates.mockReturnValue([ { pluginId: "matrix", kind: "channel-configured", channelId: "matrix" }, diff --git a/src/commands/doctor/shared/release-configured-plugin-installs.ts b/src/commands/doctor/shared/release-configured-plugin-installs.ts index 88a13f98bf1..b1bfbf6894a 100644 --- a/src/commands/doctor/shared/release-configured-plugin-installs.ts +++ b/src/commands/doctor/shared/release-configured-plugin-installs.ts @@ -336,17 +336,31 @@ export async function maybeRunConfiguredPluginInstallReleaseStep(params: { completed: boolean; touchedConfig: boolean; }> { - if ( - !shouldRunConfiguredPluginInstallReleaseStep({ - currentVersion: params.currentVersion, - touchedVersion: params.touchedVersion, - }) - ) { - return { changes: [], warnings: [], completed: false, touchedConfig: false }; - } const env = params.env ?? process.env; const updateInProgress = isTruthyEnvValue(env[UPDATE_IN_PROGRESS_ENV]); const configured = collectReleaseConfiguredPluginIds({ cfg: params.cfg, env }); + const shouldRunReleaseStep = shouldRunConfiguredPluginInstallReleaseStep({ + currentVersion: params.currentVersion, + touchedVersion: params.touchedVersion, + }); + if (!shouldRunReleaseStep) { + if (configured.pluginIds.length === 0 && configured.channelIds.length === 0) { + return { changes: [], warnings: [], completed: false, touchedConfig: false }; + } + const repaired = await repairMissingPluginInstallsForIds({ + cfg: params.cfg, + pluginIds: configured.pluginIds, + channelIds: configured.channelIds, + blockedPluginIds: collectBlockedPluginIds(params.cfg), + env, + }); + return { + changes: repaired.changes, + warnings: repaired.warnings, + completed: repaired.warnings.length === 0, + touchedConfig: false, + }; + } if (configured.pluginIds.length === 0 && configured.channelIds.length === 0) { return { changes: [], warnings: [], completed: true, touchedConfig: !updateInProgress }; }