Merge pull request #4041 from mastertyko/fix/3707-unpark-db-desync

fix(gsd): repair DB-only milestone unpark state
This commit is contained in:
Jeremy McSpadden 2026-04-12 09:02:42 -05:00 committed by GitHub
commit d21d3e364d
2 changed files with 28 additions and 3 deletions

View file

@ -20,7 +20,7 @@ import {
} from "./paths.js";
import { invalidateAllCaches } from "./cache.js";
import { loadQueueOrder, saveQueueOrder } from "./queue-order.js";
import { isDbAvailable, updateMilestoneStatus } from "./gsd-db.js";
import { getMilestone, isDbAvailable, updateMilestoneStatus } from "./gsd-db.js";
import { logWarning } from "./workflow-logger.js";
// ─── Park ──────────────────────────────────────────────────────────────────
@ -77,9 +77,16 @@ export function unparkMilestone(basePath: string, milestoneId: string): boolean
if (!mDir || !existsSync(mDir)) return false;
const parkedPath = join(mDir, buildMilestoneFileName(milestoneId, "PARKED"));
if (!existsSync(parkedPath)) return false; // not parked
const hadParkedFile = existsSync(parkedPath);
const dbThinksParked = isDbAvailable() && getMilestone(milestoneId)?.status === "parked";
unlinkSync(parkedPath);
// Recover the reverse desync too: DB can still say "parked" even when the
// PARKED marker was lost on disk, and /gsd unpark should repair that state.
if (!hadParkedFile && !dbThinksParked) return false;
if (hadParkedFile) {
unlinkSync(parkedPath);
}
// Sync DB status so deriveStateFromDb picks up the unparked milestone (#2694)
if (isDbAvailable()) {
try {

View file

@ -69,6 +69,24 @@ test("unparkMilestone updates DB status to 'active' (#2694)", () => {
}
});
test("unparkMilestone repairs parked DB state when PARKED.md is missing (#3707)", () => {
const base = createBase();
try {
openDatabase(":memory:");
insertMilestone({ id: "M001", title: "Test", status: "parked" });
const unparked = unparkMilestone(base, "M001");
assert.ok(unparked, "unparkMilestone should recover DB-only parked state");
assert.equal(getMilestone("M001")!.status, "active", "DB status should be repaired to active");
closeDatabase();
} finally {
closeDatabase();
rmSync(base, { recursive: true, force: true });
}
});
test("park/unpark are safe when DB is not available (#2694 guard)", () => {
const base = createBase();
try {