fix(backup): create backups only when settings content changes

Previously, PUT /api/settings/:profile created a backup on every request
regardless of whether the content actually changed. This led to hundreds
of identical backup files accumulating in ~/.ccs/backups/.

Changes:
- Compare existing content with new content before creating backup
- Reuse computed newContent for atomic write (DRY)
- Make hook injection idempotent by checking content before write

Fixes #433
This commit is contained in:
kaitranntt
2026-02-03 19:48:00 -05:00
parent b833b149e0
commit c324e92eb4
2 changed files with 24 additions and 13 deletions
+11 -5
View File
@@ -123,11 +123,17 @@ export function ensureProfileHooks(profileName: string): boolean {
// Clean up any duplicates that may have accumulated (Windows path bug fix)
const hadDuplicates = deduplicateCcsHooks(settings);
if (hadDuplicates) {
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
if (process.env.CCS_DEBUG) {
console.error(
info(`Removed duplicate WebSearch hooks from ${profileName}.settings.json`)
);
// Re-read file to compare with modified settings (deduplicateCcsHooks mutates in-place)
const newContent = JSON.stringify(settings, null, 2);
const existingContent = fs.readFileSync(settingsPath, 'utf8');
// Only write if content actually changed
if (newContent !== existingContent) {
fs.writeFileSync(settingsPath, newContent, 'utf8');
if (process.env.CCS_DEBUG) {
console.error(
info(`Removed duplicate WebSearch hooks from ${profileName}.settings.json`)
);
}
}
}
// Update timeout if needed
+13 -8
View File
@@ -156,16 +156,21 @@ router.put('/:profile', (req: Request, res: Response): void => {
}
}
// Create backup only if file exists
// Create backup only if file exists AND content actually changed
let backupPath: string | undefined;
const newContent = JSON.stringify(settings, null, 2) + '\n';
if (fileExists) {
const backupDir = path.join(ccsDir, 'backups');
if (!fs.existsSync(backupDir)) {
fs.mkdirSync(backupDir, { recursive: true });
const existingContent = fs.readFileSync(settingsPath, 'utf8');
// Only create backup if content differs
if (existingContent !== newContent) {
const backupDir = path.join(ccsDir, 'backups');
if (!fs.existsSync(backupDir)) {
fs.mkdirSync(backupDir, { recursive: true });
}
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
backupPath = path.join(backupDir, `${profile}.${timestamp}.settings.json`);
fs.copyFileSync(settingsPath, backupPath);
}
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
backupPath = path.join(backupDir, `${profile}.${timestamp}.settings.json`);
fs.copyFileSync(settingsPath, backupPath);
}
// Ensure directory exists for new files
@@ -175,7 +180,7 @@ router.put('/:profile', (req: Request, res: Response): void => {
// Write new settings atomically
const tempPath = settingsPath + '.tmp';
fs.writeFileSync(tempPath, JSON.stringify(settings, null, 2) + '\n');
fs.writeFileSync(tempPath, newContent);
fs.renameSync(tempPath, settingsPath);
const newStat = fs.statSync(settingsPath);