fix(sync): crash-safe renames loop — record per-file failures instead of throwing (#2402)

The renames loop reimports each renamed file via importFile() but, unlike the
deletes and adds/mods loops, does not wrap the call. importFile() still throws
on content sanity-block, duplicate-slug, and missing-link endpoints, so a single
malformed renamed file throws uncaught and crashes the whole sync mid-run —
freezing the checkpoint and defeating --skip-failed. A 'skipped' result carrying
an error was also silently dropped (never recorded to failedFiles).

This wraps the reimport in try/catch and records both the throw and the
skipped-with-error case to failedFiles, matching the existing deletes/adds loop
pattern. Surfaced in the wild by a tree-wide rename (a shared/ -> system/ vault
migration, ~10.8k renames) where one malformed-YAML renamed file crashed the
entire incremental sync at rename ~4900/10857.

Co-authored-by: jaxlewis-swift <jaxlewis@swiftsolutions.ai>
This commit is contained in:
supportswift
2026-07-17 11:31:49 -07:00
committed by GitHub
co-authored by jaxlewis-swift
parent 78bc2fef09
commit 74bc8f8cd1
+16 -3
View File
@@ -2306,11 +2306,24 @@ async function performSyncInner(engine: BrainEngine, opts: SyncOpts): Promise<Sy
} catch {
// Slug doesn't exist or collision, treat as add
}
// Reimport at new path (picks up content changes)
// Reimport at new path (picks up content changes). Wrapped to match the
// deletes/adds loops: a malformed renamed file is recorded to failedFiles
// and skipped, NOT thrown uncaught. importFile still throws on content
// sanity-block, duplicate-slug, and missing-link endpoints; an uncaught
// throw here crashes the whole sync mid-run and freezes the checkpoint,
// defeating --skip-failed. A `skipped` result carrying an error is also
// captured so the failure is recorded rather than silently dropped.
const filePath = join(repoPath, to);
if (existsSync(filePath)) {
const result = await importFile(engine, filePath, to, { noEmbed, sourceId: opts.sourceId, activePack: syncActivePack });
if (result.status === 'imported') chunksCreated += result.chunks;
try {
const result = await importFile(engine, filePath, to, { noEmbed, sourceId: opts.sourceId, activePack: syncActivePack });
if (result.status === 'imported') chunksCreated += result.chunks;
else if (result.status === 'skipped' && (result as { error?: string }).error) {
failedFiles.push({ path: to, error: String((result as { error?: string }).error) });
}
} catch (e: unknown) {
failedFiles.push({ path: to, error: e instanceof Error ? e.message : String(e) });
}
}
pagesAffected.push(newSlug);
await markCompleted(to);