Skip to content

fix(forks): stop copying connector-managed knowledge base documents - #6818

Merged
icecrasher321 merged 4 commits into
stagingfrom
fix/fork-skip-connector-managed-documents
Aug 18, 2026
Merged

fix(forks): stop copying connector-managed knowledge base documents#6818
icecrasher321 merged 4 commits into
stagingfrom
fix/fork-skip-connector-managed-documents

Conversation

@icecrasher321

@icecrasher321 icecrasher321 commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

A fork copies a knowledge base's documents but never its connectors — deliberate, per copyForkResourceContainers ("the child is a content snapshot without live sync"). What was not deliberate is that connector-sourced documents were copied anyway, arriving with connector_id nulled and external_id, content_hash, source_url and the original uploaded_at intact.

That copy is unreachable by the sync engine, which scopes every existing / tombstone / exclusion lookup to eq(document.connectorId, connectorId) (sync-engine.ts:828-885). It can never be updated, reconciled, or purged. doc_connector_external_id_idxUNIQUE (connector_id, external_id) WHERE deleted_at IS NULL — does not constrain it either, because Postgres treats NULLs as distinct.

So attaching a connector in the child re-ingests every page as a new row on top of the snapshot. And since the copy loop filtered only deleted_at / archived_at, each hop also re-copied the previous hop's orphans:

inherited (fork_document_*) connector (UUID) total
prod 0 56 56
UAT (fork + attach connector) 56 56 112
staging (fork + attach connector) 112 56 168

Three rows per page, so a knowledge search returns the same page three times — one of them frozen at the fork date, serving content nothing will ever refresh. This was reported from a real prod → UAT → staging chain: 191 documents for ~56 Confluence pages.

The change

Exclude connector-managed documents at all four doors a document can enter a fork through:

Site Change
copyForkResourceContent whole-KB page query isNull(document.connectorId)
createForkDocumentPlaceholders (in the fork tx) isNull(document.connectorId)
planForkMappedKbDocumentCopies (sync-only, doc → already-mapped KB) isNull(document.connectorId)
contentPlan.documents fill loop explicit source.connectorId guard

The placeholder path matters as much as the copy loop. Filtering only the content phase would leave a referenced connector document with an archived placeholder and a persisted knowledge_document mapping pointing at it, with nothing behind it to ever fill it. Skipped on both sides, its document-selector reference clears like any other uncopied document's — existing, documented behavior.

The fourth guard covers one case only: a payload planned by a pre-change worker and filled by a new one mid-rollout. It throws into the existing per-document cleanup, which drops the placeholder and clears its references — the same outcome the new planner produces.

A document whose connector was deleted in the source already has a null connector_id (the FK is ON DELETE SET NULL) and is static content there too, so it still copies. That falls out of the predicate rather than needing a special case.

One count(*) per copied KB logs what was left behind — a fully connector-synced KB now forks to zero documents, and without this there would be nothing anywhere explaining why.

Docs

New #### Connector-synced documents are not copied under "Knowledge bases and documents": the sharp edge in a warn callout, the reasoning, and the two workarounds (re-add the connector for live content; download-and-upload for a frozen snapshot). The at-a-glance row links to it.

Also corrected the adjacent line, which claimed "Documents that the forked workflows actually reference are included" — the fork copies every document in the KB; only the placeholder pre-creation was ever reference-scoped.

Not in scope

This stops new duplication; it does not clean up KBs already in this state. No follow-up is needed — the known-affected knowledge bases have already been corrected manually, out of band. No backfill or migration ships with this PR.

Type of Change

  • Bug fix

Testing

bun run test ee/workspace-forking39 files, 601 tests passed. type-check, biome check and check:api-validation all clean.

Four new tests. The predicates are the whole fix, and the row-queue mocks return whatever is enqueued regardless of the where, so three of them assert the condition tree via flattenMockConditions + schemaMock — the helper that exists for exactly this:

  • whole-KB page query excludes connector_id
  • placeholder pre-creation excludes it, and records no knowledge_document mapping
  • mapped-KB candidate query excludes it
  • U-docs fill refuses a connector-managed source: charges nothing, downloads nothing, reports a knowledge-document failure for cleanup

Also flipped the planForkMappedKbDocumentCopies fixture from connectorId: 'connector-1' to null — it was describing a document the planner must now never place.

Reviewers should focus on the placeholder/content-phase agreement (they must skip the same set or a mapping dangles) and on whether losing connector content at fork time is the right trade for the affected teams — a fully-synced KB now forks empty, which is a real behavior change the docs callout is there to set expectations for.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Aug 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview Aug 18, 2026 9:16pm

Request Review

@cursor

cursor Bot commented Aug 18, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes fork/sync knowledge-base document behavior and is a visible behavior change for connector-only KBs, but scope is bounded to fork copy logic with extensive tests and rollout safeguards—not auth or payment paths.

Overview
Forks already copy knowledge bases without live connectors, but connector-synced documents were still copied, leaving detached rows the sync engine never updates. Re-attaching a connector in the child then stacked new ingestions on those snapshots, so chained forks could return the same page multiple times in search.

This PR excludes connector-managed documents (connector_id set) at every fork path: full-KB bulk copy, referenced-document placeholders in the fork transaction, sync-only copies into an already-mapped KB, and an explicit guard when old workers still queue U-doc fills during rollout. Placeholder and content phases skip the same set so no dangling knowledge_document mappings point at empty archived rows.

Rolling deploy: stale placeholders planned before the change are reconciled (mapping dropped, failures reported); probe/count helpers are best-effort so transient DB errors do not roll back a good KB copy.

Docs add a warn callout that fully connector-synced KBs fork with no documents, plus workarounds (re-add connector or download/upload). Documents whose connector was deleted in the source still copy (connector_id null).

Reviewed by Cursor Bugbot for commit ae315cc. Configure here.

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR prevents connector-managed knowledge documents from being detached and duplicated across workspace forks.

  • Filters connector-managed documents from whole-KB, placeholder, and mapped-KB planning paths.
  • Reconciles mixed-version plans and documents the resulting fork behavior.
  • Adds coverage for filtering, rollout cleanup, and non-fatal diagnostic failures.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts Consistently excludes connector-managed documents across planning and copying while handling mixed-version placeholders and non-critical diagnostic failures.
apps/sim/ee/workspace-forking/lib/copy/copy-resources.test.ts Adds focused tests for connector filtering, stale-plan reconciliation, and best-effort diagnostic behavior.
apps/docs/content/docs/en/platform/enterprise/forks.mdx Clearly documents that connector-synced documents do not cross fork boundaries and explains available alternatives.

Reviews (4): Last reviewed commit: "fix(forks): make the stale-plan probe be..." | Re-trigger Greptile

Comment thread apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts
Comment thread apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts Outdated
Comment thread apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile the last two rounds both reviewed 678b5e9533. The P1 you raised (stale whole-KB placeholders from a pre-change plan) is fixed in a4a583f72a, which is now head — please re-review against that commit.

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts
icecrasher321 and others added 4 commits August 18, 2026 14:09
A fork copies a KB's documents but never its connectors, so a
connector-sourced document arrives with `connector_id` nulled and its
`external_id` intact. The sync engine keys every existing/tombstone/
exclusion lookup off `connector_id`, so that copy is invisible to it -
never updated, reconciled, or purged - and `doc_connector_external_id_idx`
does not constrain it either, since its `connector_id` is NULL.

Attaching a connector in the child then re-ingests every page as a NEW
row on top of the snapshot. Each fork hop re-copies the previous hop's
orphans and adds one more generation, so a prod -> UAT -> staging chain
leaves three rows per page and a knowledge search returns the same page
three times, one of them serving content frozen at the fork date.

Exclude connector-managed documents from all four doors a document can
enter a fork through: the whole-KB content copy, the in-transaction
placeholder pre-creation, the sync-only copy into an already-mapped KB,
and the content fill (guarded for payloads planned by a pre-change
worker mid-rollout). The placeholder path matters as much as the copy
loop - filtering only the content phase would leave a permanently
archived row behind a persisted `knowledge_document` mapping. Skipped on
both sides, the reference clears like any other uncopied document's.

A document whose connector was deleted already has a null `connector_id`
(the FK is ON DELETE SET NULL) and is static in the source too, so it
still copies. One count(*) per copied KB logs what was left behind, since
a fully connector-synced KB now forks to zero documents.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The connector-managed count feeds a log line, but it sat inside the KB's
try block, so a transient failure on a COUNT(*) would roll back a copy
that had otherwise succeeded and clear every reference to it.

Move it into a helper that swallows its own error. Counting is not
copying: only the copy itself may fail a resource. Test proven red by
removing the catch - the mutation reports a knowledge-base failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The mapped-KB fill guarded a pre-change plan, but the full-KB path did
not: a placeholder planned by an old worker for a connector-managed
document is simply no longer returned by the page query, so nothing fills
it and it stays archived behind a live mapping that a remapped
document-selector still resolves to.

Report those child ids as failed documents so the shared cleanup clears
their references and drops the rows, and delete their persisted identity
so a later sync does not resolve to a row cleanup removes. Keyed on the
SOURCE being connector-managed, which can never become copyable, so it
cannot race a concurrent attempt mid-fill the way a "source is gone"
check could.

The mapping drop is now one helper shared with the mapped-KB catch.
Test proven red by removing the reconciliation block.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The probe ran inside the KB try, so a transient SELECT would reach the
catch, roll back a complete copy, delete the child base, and clear every
reference to it. Weighing it as "load-bearing, so fail closed" was wrong:
the probe runs on EVERY copied KB that has referenced documents, while
the state it repairs exists only inside a rollout window. Failing closed
traded a common-path outage against a rare-squared one.

It now swallows its own failure with a loud error log, leaving that
pre-existing state in place rather than destroying a good copy. Test
proven red by removing the catch - the mutation reports the KB failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@icecrasher321
icecrasher321 force-pushed the fix/fork-skip-connector-managed-documents branch from a4a583f to ae315cc Compare August 18, 2026 21:10
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit ae315cc. Configure here.

@icecrasher321
icecrasher321 merged commit 3a03774 into staging Aug 18, 2026
30 of 31 checks passed
@icecrasher321
icecrasher321 deleted the fix/fork-skip-connector-managed-documents branch August 18, 2026 21:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant