fix(config-backup): read the backup folder once per pass

Merge-time fix for #661. A pass listed the folder and parsed every manifest
three times: for the recorded-retention fallback, for the change check and
for the prune (and restore listed it twice more). On a network folder that
is every manifest read three times every five minutes.

runConfigBackupPass (config-backup.ts) now reads the folder once, shares the
names with the temp sweep and the manifests with withRecordedRetention, the
snapshot and the prune, and prepends the snapshot it just made (always the
newest, by the date floor) to the listing retention counts. runConfigBackup
in the route keeps the settings read and the bounded probe and calls it.
restoreConfigBackup lists once for the recorded retention and the pre-restore
snapshot. The exported createConfigBackup, latestBackupRetention,
withRecordedRetention and pruneConfigBackups keep their signatures
(latestBackupRetention and withRecordedRetention take an optional pre-read
listing). A recording node:fs test pins one
readdir and one read per manifest per pass, and that the new snapshot counts
toward retention.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-22 19:17:36 +02:00
1 parent 922b077cc9
commit 8a216df12f
3 files changed
+248 -74

No files matched your search

+6 -16
View File
@@ -34,18 +34,15 @@ import {
ConfigBackupCorruptError,
ConfigBackupNotFoundError,
ConfigBackupPartialRestoreError,
createConfigBackup,
defaultConfigBackupDir,
diffConfigBackups,
listConfigBackups,
pruneConfigBackups,
readConfigBackupSettings,
resolveConfigBackupDir,
restoreConfigBackup,
configBackupOrigin,
runConfigBackupPass,
withRecordedRetention,
readConfigBackupOrigin,
sweepConfigBackupTemps,
type ConfigBackupResult,
type ConfigBackupSettings,
type ConfigBackupSummary,
@@ -140,24 +137,17 @@ export async function runConfigBackup(
if (!fromFile.enabled && !opts.force) return null;
const dir = resolveConfigBackupDir(fromFile, getDataDir());
await probeBackupDir(fromFile, dir);
sweepConfigBackupTemps(dir);
const origin = configBackupOrigin(getDataDir());
// No backup settings in the file (reset, or unreadable): keep the retention the newest snapshot recorded.
const settings = withRecordedRetention(fromFile, unreadable ? {} : raw, dir, origin);
const result = createConfigBackup({
return runConfigBackupPass({
dataDir: getDataDir(),
backupDir: dir,
reason,
appVersion: APP_VERSION,
force: opts.force,
keepCount: settings.keepCount,
keepDays: settings.keepDays,
settings: fromFile,
raw: unreadable ? {} : raw,
// An unreadable settings.json still gets its snapshot, but no pass deletes anything on its say-so.
prune: !unreadable,
});
// An unreadable settings.json still gets its snapshot, but no pass deletes anything on its say-so.
const removed = unreadable
? []
: pruneConfigBackups(dir, { keepCount: settings.keepCount, keepDays: settings.keepDays, origin });
return { result, removed };
}
export function registerConfigBackupRoutes(app: FastifyInstance): void {