toeverything/AFFiNE · error · CopilotSelectedSourcesLimitExceeded

copilot_selected_sources_limit_exceeded

copilot_selected_sources_limit_exceeded

Error message

Too many or too much content was selected. Select fewer sources and try again.

What it means

Thrown by BackendRuntimeEmbeddingJob.prepareSelectedDocuments (job.ts:90-91) when a Copilot chat turn uses retrieval scope mode 'required' and the count of unique selected doc ids exceeds SELECTED_DOCUMENT_LIMIT (64). It is an 'invalid_input' UserFriendlyError raised before any embedding sync or model call starts, meaning the request was rejected purely on selection size.

Solutions

  1. Reduce the selection to at most 64 distinct docs and resubmit the turn.
  2. In the context picker, deselect large or irrelevant docs; prefer a small focused set.
  3. If more content is truly needed, split the work into multiple turns each with a subset of sources, or reduce per-doc content (see the separate 20,000-unit / 16MiB aggregate limits).

Example fix

// before
const requiredDocIds = allDocIds; // 120 docs -> copilot_selected_sources_limit_exceeded
await chat({ retrieval: { mode: 'required', requiredDocIds } });

// after
const SELECTED_DOCUMENT_LIMIT = 64;
const requiredDocIds = [...new Set(allDocIds)].slice(0, SELECTED_DOCUMENT_LIMIT);
await chat({ retrieval: { mode: 'required', requiredDocIds } });
Defensive patterns

Strategy: validation

Validate before calling

const SELECTED_DOCUMENT_LIMIT = 64;
const distinct = [...new Set(requiredDocIds)];
if (distinct.length > SELECTED_DOCUMENT_LIMIT) {
  throw new Error(`Select at most ${SELECTED_DOCUMENT_LIMIT} docs (got ${distinct.length})`);
}
await prepareSelectedDocuments(workspaceId, distinct);

Try / catch

try {
  await prepareSelectedDocuments(workspaceId, requiredDocIds);
} catch (e) {
  if (getErrorCode(e) === 'copilot_selected_sources_limit_exceeded') {
    // prompt user to shrink selection; do NOT retry unchanged
  } else throw e;
}

Prevention

When it happens

Trigger: A chat turn whose scopeSnapshot.retrieval.mode === 'required' passes scope.requiredDocIds to prepareSelectedDocuments; after server-side dedupe ([...new Set(docIds)]) the distinct count is greater than 64.

Common situations: Selecting an entire folder or workspace as AI context in the doc picker; a UI 'select all' that forwards every doc id; assuming duplicates count against the limit (they are deduped first, so only distinct docs matter).

Related errors


AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18). Data as JSON: /api/errors/09fa62b5b7643494. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/backend-runtime/job.ts:39

const SELECTED_DOCUMENT_TEXT_BYTE_LIMIT = 16 * 1024 * 1024;
const SELECTED_DOCUMENT_PRIORITY = 1000;
const SELECTED_DOCUMENT_WAIT_MS = 90_000;

@Injectable()
export class BackendRuntimeEmbeddingService {
  constructor(
    private readonly rt: BackendRuntimeProvider,
    private readonly models: Models
  ) {}

  async syncDocument(workspaceId: string, docId: string) {
    await this.syncDocuments(workspaceId, [docId], true);
  }

  async prepareSelectedDocuments(workspaceId: string, docIds: string[]) {
    const selectedDocIds = [...new Set(docIds)];
    if (selectedDocIds.length > SELECTED_DOCUMENT_LIMIT) {
      throw new CopilotSelectedSourcesLimitExceeded();
    }
    try {
      await this.syncDocuments(workspaceId, selectedDocIds, false, {
        priority: SELECTED_DOCUMENT_PRIORITY,
        waitForReadyMs: SELECTED_DOCUMENT_WAIT_MS,
      });
    } catch (error) {
      throw this.mapSelectedSourceError(error);
    }
  }

  private mapSelectedSourceError(error: unknown) {
    const message = error instanceof Error ? error.message : String(error);
    if (message.includes('embedding_selected_sources_processing')) {
      return new CopilotSelectedSourcesProcessing();
    }
    if (message.includes('embedding_selected_sources_failed')) {
      return new CopilotSelectedSourcesFailed();

View on GitHub (pinned to 2af30773ae)