fix(KB-610): fix race condition in allocateId and ensure config row exists

- Fix race condition in allocateId and ensure config row exists for reliable settings save
- Add comprehensive settings save tests in store.test.ts and SettingsModal.test.tsx
- Add changeset documenting the settings save fix
- Remove backup functionality from core and CLI (backup.ts, backup.test.ts, backup command)
- Update AGENTS.md to remove obsolete backup documentation
This commit is contained in:
gsxdsm
2026-03-31 15:47:12 -07:00
parent a6f99f90bb
commit 12e90ae8fb
4 changed files with 164 additions and 8 deletions

View File

@@ -517,14 +517,17 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
}
private async writeConfig(config: BoardConfig): Promise<void> {
const now = new Date().toISOString();
// Use INSERT OR REPLACE to ensure the config row exists (handles edge case where row is missing)
this.db.prepare(
`UPDATE config SET nextId = ?, nextWorkflowStepId = ?, settings = ?, workflowSteps = ?, updatedAt = ? WHERE id = 1`,
`INSERT OR REPLACE INTO config (id, nextId, nextWorkflowStepId, settings, workflowSteps, updatedAt)
VALUES (1, ?, ?, ?, ?, ?)`,
).run(
config.nextId || 1,
config.nextWorkflowStepId || 1,
JSON.stringify(config.settings || {}),
JSON.stringify(config.workflowSteps || []),
new Date().toISOString(),
now,
);
this.db.bumpLastModified();
// Also write config.json to disk for backward compatibility
@@ -549,12 +552,17 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
return taskId;
});
// Sync config.json to disk for backward compatibility
try {
const config = await this.readConfig();
await writeFile(this.configPath, JSON.stringify(config, null, 2));
} catch {
// Non-fatal
}
// Use withConfigLock to prevent race conditions when creating tasks in parallel
await this.withConfigLock(async () => {
try {
const config = await this.readConfig();
const tmpPath = this.configPath + ".tmp";
await writeFile(tmpPath, JSON.stringify(config, null, 2));
await rename(tmpPath, this.configPath);
} catch {
// Non-fatal: SQLite is the primary store
}
});
return id;
}