Skip to content

Commit ceb2111

Browse files
committed
fix(state-migrations): use generic conflict warning text
Per ClawSweeper review feedback [P3]: the previous text 'shared SQLite state has newer metadata for' was misleading — conflicts can arise from mismatched floating selectors, package renames, or malformed legacy spec metadata, not necessarily because SQLite is newer. Use a generic conflict description that accurately covers all conflict reasons.
1 parent 1a3ce7c commit ceb2111

2 files changed

Lines changed: 26 additions & 11 deletions

File tree

src/commands/doctor-state-migrations.test.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// Doctor state migration tests cover legacy state moves, archive markers, and repair behavior.
21
import fs from "node:fs";
32
import os from "node:os";
43
import path from "node:path";
@@ -1511,18 +1510,35 @@ describe("doctor legacy state migrations", () => {
15111510
current: InstalledPluginInstallRecordInfo;
15121511
legacy: InstalledPluginInstallRecordInfo;
15131512
}>) {
1514-
it(`keeps legacy plugin install index when same-version npm records ${fixture.label}`, async () => {
1513+
it(`archives legacy plugin install index and warns (once) when same-version npm records ${fixture.label}`, async () => {
15151514
const root = await makeTempRoot();
15161515
await writeExistingPluginInstallIndex(root, { demo: fixture.current });
15171516
const sourcePath = writeLegacyPluginInstallIndex(root, { demo: fixture.legacy });
15181517

15191518
const result = await runLegacyStateMigrationsForRoot(root);
15201519

1520+
// The conflict warning is emitted once, then the legacy file is archived so it
1521+
// cannot trigger the same warning on subsequent gateway startups (fixes #90213).
15211522
expect(result.warnings).toStrictEqual([
1522-
"Left plugin install index in place because shared SQLite state has conflicting plugin install metadata for: demo",
1523+
"Archived plugin install index after detecting conflicting plugin install metadata for: demo",
15231524
]);
1524-
expect(fs.existsSync(sourcePath)).toBe(true);
1525-
expect(fs.existsSync(`${sourcePath}.migrated`)).toBe(false);
1525+
expect(fs.existsSync(sourcePath)).toBe(false);
1526+
expect(fs.existsSync(`${sourcePath}.migrated`)).toBe(true);
1527+
});
1528+
1529+
it(`does not re-emit plugin install conflict warning on subsequent runs when ${fixture.label}`, async () => {
1530+
const root = await makeTempRoot();
1531+
await writeExistingPluginInstallIndex(root, { demo: fixture.current });
1532+
const sourcePath = writeLegacyPluginInstallIndex(root, { demo: fixture.legacy });
1533+
1534+
// First run: archives the legacy file and emits the warning once.
1535+
const first = await runLegacyStateMigrationsForRoot(root);
1536+
expect(first.warnings).toHaveLength(1);
1537+
expect(fs.existsSync(sourcePath)).toBe(false);
1538+
1539+
// Second run: no legacy file exists, so migration is skipped entirely — no repeated warning.
1540+
const second = await runLegacyStateMigrationsForRoot(root);
1541+
expect(second.warnings).toHaveLength(0);
15261542
});
15271543
}
15281544

src/infra/state-migrations.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,12 +1499,11 @@ async function migrateLegacyInstalledPluginIndex(params: {
14991499
}
15001500
}
15011501
if (merged.conflicts.length > 0) {
1502-
return {
1503-
changes,
1504-
warnings: [
1505-
`Left plugin install index in place because shared SQLite state has conflicting plugin install metadata for: ${merged.conflicts.join(", ")}`,
1506-
],
1507-
};
1502+
warnings.push(
1503+
`Archived plugin install index after detecting conflicting plugin install metadata for: ${merged.conflicts.join(", ")}`,
1504+
);
1505+
archiveLegacyInstalledPluginIndex({ sourcePath, changes, warnings });
1506+
return { changes, warnings };
15081507
}
15091508
}
15101509

0 commit comments

Comments
 (0)