Skip to content

Commit 4b14143

Browse files
committed
fix(knowledge): bound tombstone-forced full syncs, resurrect kept docs on connector delete
Two more real findings from this review round: - A document whose refresh keeps failing every sync (e.g. permanently oversized) never resurrects and never hard-deletes (it's present in the listing, just unreadable) — correct on its own, but it also never stops being counted by hasTombstonedDocs, so it would force a full listing for this connector forever, permanently disabling incremental sync on account of one stuck document. Bounded the check to the same RETRY_WINDOW_DAYS already used for the stuck-document retry sweep below: past the window, this connector stops forcing full syncs on the stuck document's account. The document itself is unaffected — it stays tombstoned either way, matching the existing 'last-known-good forever' tolerance this codebase already accepts for any document whose hydration keeps failing. - The connector-DELETE route's deleteDocuments=false path (kept docs) counted tombstoned documents but never resolved them one way or the other. With the connector gone, there's no future sync left to ever confirm or resurrect them, so they'd become permanent invisible orphans holding storage forever. Since 'kept' documents become normal standalone KB entries once detached from their connector, resurrect any pending-removal ones as part of that transition — consistent with what happens to their non-tombstoned sibling documents.
1 parent e2eee0f commit 4b14143

2 files changed

Lines changed: 17 additions & 2 deletions

File tree

apps/sim/app/api/knowledge/[id]/connectors/[connectorId]/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,12 +335,18 @@ export const DELETE = withRouteHandler(async (request: NextRequest, { params }:
335335
.from(document)
336336
.where(and(eq(document.connectorId, connectorId), isNull(document.archivedAt)))
337337

338+
const documentIds = docs.map((doc) => doc.id)
338339
if (deleteDocuments) {
339-
const documentIds = docs.map((doc) => doc.id)
340340
if (documentIds.length > 0) {
341341
await tx.delete(embedding).where(inArray(embedding.documentId, documentIds))
342342
await tx.delete(document).where(inArray(document.id, documentIds))
343343
}
344+
} else if (documentIds.length > 0) {
345+
// Kept documents become normal standalone KB entries once their connector
346+
// is gone — resurrect any pending-removal ones rather than leaving them
347+
// invisible tombstones with no future sync left to ever confirm or
348+
// resurrect them.
349+
await tx.update(document).set({ deletedAt: null }).where(inArray(document.id, documentIds))
344350
}
345351

346352
const deletedConnectors = await tx

apps/sim/lib/knowledge/connectors/sync-engine.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,14 +516,23 @@ export async function executeSync(
516516
let hasMore = true
517517
const syncContext: Record<string, unknown> = { syncRunId: generateId() }
518518

519+
/**
520+
* Bounded to the same retry window as the stuck-document retry below: a
521+
* document whose refresh keeps failing every sync (e.g. permanently
522+
* oversized) would otherwise be a tombstone that never resolves, forcing a
523+
* full listing — and its listing-time overhead — for this connector
524+
* forever. Past the window, this connector stops forcing full syncs on its
525+
* account; the document itself is unaffected and stays tombstoned either way.
526+
*/
519527
const hasTombstonedDocs = await db
520528
.select({ id: document.id })
521529
.from(document)
522530
.where(
523531
and(
524532
eq(document.connectorId, connectorId),
525533
isNull(document.archivedAt),
526-
isNotNull(document.deletedAt)
534+
isNotNull(document.deletedAt),
535+
gt(document.deletedAt, new Date(Date.now() - RETRY_WINDOW_DAYS * 24 * 60 * 60 * 1000))
527536
)
528537
)
529538
.limit(1)

0 commit comments

Comments
 (0)