paperclipai/paperclip · error · Error
Paperclip ingestion fan-out exceeds the hard cap of ${MAX_PA
Error message
Paperclip ingestion fan-out exceeds the hard cap of ${MAX_PAPERCLIP_DISTILLATION_FAN_OUT} enabled profiles. What it means
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.
Source
Thrown at packages/plugins/plugin-llm-wiki/src/wiki/core.ts:2483
if (project.managedByPlugin?.pluginKey === PLUGIN_ID) return false;
if (project.managedByPlugin?.resourceKey === WIKI_PROJECT_KEY) return false;
return true;
}
function projectActivityTimestamp(project: Project): string {
return isoString(project.updatedAt) ?? new Date().toISOString();
}
export async function enableActiveProjectDistillation(ctx: PluginContext, input: {
companyId: string;
wikiId?: string | null;
spaceSlug?: string | null;
limit?: number | null;
}): Promise<EnableActiveProjectDistillationResult> {
const wikiId = normalizeWikiId(input.wikiId);
const space = await requirePaperclipIngestionPolicy(ctx, { companyId: input.companyId, wikiId, spaceSlug: input.spaceSlug }, "candidate_search", { requireEnabledProfile: true });
if (typeof input.limit === "number" && Number.isFinite(input.limit) && Math.floor(input.limit) > MAX_PAPERCLIP_DISTILLATION_FAN_OUT) {
throw new Error(`Paperclip ingestion fan-out exceeds the hard cap of ${MAX_PAPERCLIP_DISTILLATION_FAN_OUT} enabled profiles.`);
}
const limit = normalizeLimit(input.limit ?? 3, 3, 25);
const projects = await ctx.projects.list({ companyId: input.companyId, limit: 200 });
const activeProjects = projects
.filter(isActiveDistillationProject)
.sort((a, b) => projectActivityTimestamp(b).localeCompare(projectActivityTimestamp(a)))
.slice(0, limit);
const selectedProjects: EnableActiveProjectDistillationResult["selectedProjects"] = [];
for (const project of activeProjects) {
const observedAt = projectActivityTimestamp(project);
const cursorId = await upsertPaperclipDistillationCursor(ctx, {
companyId: input.companyId,
wikiId,
spaceId: space.id,
projectId: project.id,
rootIssueId: null,
observedAt,View on GitHub (pinned to 67001ec6eb)
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.
Example fix
// before
enableActiveProjectDistillation(ctx, { companyId, limit: 200 });
// after
enableActiveProjectDistillation(ctx, { companyId, limit: 25 });
// or omit limit entirely for the default of 3 Defensive patterns
Strategy: validation
Validate before calling
const MAX_FAN_OUT = 25;
function safeDistillationLimit(input: number | null | undefined): number | null {
if (input == null) return null; // accept default of 3
if (!Number.isFinite(input) || Math.floor(input) > MAX_FAN_OUT) {
throw new Error(`limit must be a finite number <= ${MAX_FAN_OUT}`);
}
return Math.floor(input);
} Type guard
function isValidFanOutLimit(value: unknown): value is number {
return typeof value === 'number' && Number.isFinite(value) && Math.floor(value) >= 1 && Math.floor(value) <= 25;
} Try / catch
try {
await enableActiveProjectDistillation(ctx, { companyId, limit });
} catch (err) {
if (err instanceof Error && /fan-out exceeds the hard cap/.test(err.message)) {
// clamp and retry, or surface to operator
} else throw err;
} Prevention
- Default to omitting limit so normalizeLimit applies its 3..25 clamp.
- Validate caller-supplied limits against MAX_PAPERCLIP_DISTILLATION_FAN_OUT before invoking.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Backfill work items must target a projectId or rootIssueId;
- projectId or rootIssueId is required
- status must be succeeded, failed, or review_required
- runId is required
- projectId or rootIssueId is required
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/5abb9c2a5b14daea.
Report an issue: GitHub.