{"record":{"id":"5abb9c2a5b14daea","repo":"paperclipai/paperclip","slug":"paperclip-ingestion-fan-out-exceeds-the-hard-cap-o","errorCode":null,"errorMessage":"Paperclip ingestion fan-out exceeds the hard cap of ${MAX_PAPERCLIP_DISTILLATION_FAN_OUT} enabled profiles.","messagePattern":"Paperclip ingestion fan-out exceeds the hard cap of (.+?) enabled profiles\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugins/plugin-llm-wiki/src/wiki/core.ts","lineNumber":2483,"sourceCode":"  if (project.managedByPlugin?.pluginKey === PLUGIN_ID) return false;\n  if (project.managedByPlugin?.resourceKey === WIKI_PROJECT_KEY) return false;\n  return true;\n}\n\nfunction projectActivityTimestamp(project: Project): string {\n  return isoString(project.updatedAt) ?? new Date().toISOString();\n}\n\nexport async function enableActiveProjectDistillation(ctx: PluginContext, input: {\n  companyId: string;\n  wikiId?: string | null;\n  spaceSlug?: string | null;\n  limit?: number | null;\n}): Promise<EnableActiveProjectDistillationResult> {\n  const wikiId = normalizeWikiId(input.wikiId);\n  const space = await requirePaperclipIngestionPolicy(ctx, { companyId: input.companyId, wikiId, spaceSlug: input.spaceSlug }, \"candidate_search\", { requireEnabledProfile: true });\n  if (typeof input.limit === \"number\" && Number.isFinite(input.limit) && Math.floor(input.limit) > MAX_PAPERCLIP_DISTILLATION_FAN_OUT) {\n    throw new Error(`Paperclip ingestion fan-out exceeds the hard cap of ${MAX_PAPERCLIP_DISTILLATION_FAN_OUT} enabled profiles.`);\n  }\n  const limit = normalizeLimit(input.limit ?? 3, 3, 25);\n  const projects = await ctx.projects.list({ companyId: input.companyId, limit: 200 });\n  const activeProjects = projects\n    .filter(isActiveDistillationProject)\n    .sort((a, b) => projectActivityTimestamp(b).localeCompare(projectActivityTimestamp(a)))\n    .slice(0, limit);\n\n  const selectedProjects: EnableActiveProjectDistillationResult[\"selectedProjects\"] = [];\n  for (const project of activeProjects) {\n    const observedAt = projectActivityTimestamp(project);\n    const cursorId = await upsertPaperclipDistillationCursor(ctx, {\n      companyId: input.companyId,\n      wikiId,\n      spaceId: space.id,\n      projectId: project.id,\n      rootIssueId: null,\n      observedAt,","sourceCodeStart":2465,"sourceCodeEnd":2501,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/plugins/plugin-llm-wiki/src/wiki/core.ts#L2465-L2501","documentation":"Thrown by enableActiveProjectDistillation when the caller-supplied limit for simultaneously-distilled active projects exceeds MAX_PAPERCLIP_DISTILLATION_FAN_OUT (25). The cap exists because each enabled profile triggers LLM distillation work; an unbounded fan-out would explode cost and worker load. The check runs before any DB write so the request is rejected atomically.","triggerScenarios":"Calling the plugin action enable-active-project-distillation (or its backing export) with input.limit set to a finite number greater than 25. The guard is Math.floor(input.limit) > 25, evaluated only when input.limit is a finite number.","commonSituations":"An operator bumps the distillation limit in a routine/issue param to 'catch up' on backlog; a UI slider or config default was raised above 25; passing limit: 200 (the list() fetch size) by mistake instead of the distillation limit.","solutions":["Set input.limit to a value between 1 and 25 (inclusive), or omit it to accept the default of 3 via normalizeLimit(input.limit ?? 3, 3, 25).","If you genuinely need broader coverage, run enableActiveProjectDistillation multiple times with different project filters rather than raising the cap.","If 25 is wrong for your deployment, change the MAX_PAPERCLIP_DISTILLATION_FAN_OUT constant in packages/plugins/plugin-llm-wiki/src/wiki/core.ts:33 and document the cost implications."],"exampleFix":"// before\nenableActiveProjectDistillation(ctx, { companyId, limit: 200 });\n// after\nenableActiveProjectDistillation(ctx, { companyId, limit: 25 });\n// or omit limit entirely for the default of 3","handlingStrategy":"validation","validationCode":"const MAX_FAN_OUT = 25;\nfunction safeDistillationLimit(input: number | null | undefined): number | null {\n  if (input == null) return null; // accept default of 3\n  if (!Number.isFinite(input) || Math.floor(input) > MAX_FAN_OUT) {\n    throw new Error(`limit must be a finite number <= ${MAX_FAN_OUT}`);\n  }\n  return Math.floor(input);\n}","typeGuard":"function isValidFanOutLimit(value: unknown): value is number {\n  return typeof value === 'number' && Number.isFinite(value) && Math.floor(value) >= 1 && Math.floor(value) <= 25;\n}","tryCatchPattern":"try {\n  await enableActiveProjectDistillation(ctx, { companyId, limit });\n} catch (err) {\n  if (err instanceof Error && /fan-out exceeds the hard cap/.test(err.message)) {\n    // clamp and retry, or surface to operator\n  } else throw err;\n}","preventionTips":["Default to omitting limit so normalizeLimit applies its 3..25 clamp.","Validate caller-supplied limits against MAX_PAPERCLIP_DISTILLATION_FAN_OUT before invoking."],"tags":["paperclip","distillation","validation","config"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}