paperclipai/paperclip · error · Error

Backfill work items must target a projectId or rootIssueId;

Error message

Backfill work items must target a projectId or rootIssueId; whole-company backfill is not allowed.

What it means

Thrown by createPaperclipDistillationWorkItem when kind === 'backfill' but the resolved scope has neither projectId nor rootIssueId. Backfill must target a single project or root-issue subtree; whole-company backfill is intentionally disallowed to keep distillation cost and cursor advancement bounded. The scope is derived by paperclipCursorScopeMetadata(input).

Source

Thrown at packages/plugins/plugin-llm-wiki/src/wiki/core.ts:2904

          AND id = $6`,
      [input.companyId, wikiId, input.runId, input.sourceWindowEnd, input.sourceHash, input.cursorId, space.id],
    );
  }

  return {
    status: input.status,
    cursorAdvanced: input.status === "succeeded" && Boolean(input.cursorId && input.sourceHash && input.sourceWindowEnd),
  };
}

export async function createPaperclipDistillationWorkItem(ctx: PluginContext, input: PaperclipDistillationWorkItemInput) {
  const wikiId = normalizeWikiId(input.wikiId);
  assertPaperclipSourceScopePayload(input);
  const space = await requirePaperclipIngestionPolicy(ctx, { companyId: input.companyId, wikiId, spaceSlug: input.spaceSlug }, "queue", { requireEnabledProfile: true });
  const itemId = randomUUID();
  const scope = paperclipCursorScopeMetadata(input);
  if (input.kind === "backfill" && !scope.projectId && !scope.rootIssueId) {
    throw new Error("Backfill work items must target a projectId or rootIssueId; whole-company backfill is not allowed.");
  }
  await ctx.db.execute(
    `INSERT INTO ${distillationWorkItemTable(ctx)} AS paperclip_distillation_work_items
       (id, company_id, wiki_id, space_id, work_item_kind, status, priority, project_id, root_issue_id, requested_by_issue_id, idempotency_key, metadata)
     VALUES ($1, $2, $3, $11, $4, 'pending', $5, $6, $7, $8, $9, $10::jsonb)
     ON CONFLICT (company_id, wiki_id, space_id, idempotency_key)
     DO UPDATE SET priority = EXCLUDED.priority,
                   metadata = paperclip_distillation_work_items.metadata || EXCLUDED.metadata,
                   updated_at = now()`,
    [
      itemId,
      input.companyId,
      wikiId,
      input.kind,
      input.priority ?? "medium",
      scope.projectId,
      scope.rootIssueId,
      input.requestedByIssueId ?? null,

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Provide exactly one of projectId or rootIssueId on the backfill request.
  2. If you need company-wide coverage, enumerate projects via ctx.projects.list and submit one backfill work item per projectId.
  3. For issue-tree backfill, pass rootIssueId of the top-level issue, not a leaf issue id.

Example fix

// before
createPaperclipDistillationWorkItem(ctx, { companyId, kind: 'backfill' });
// after
createPaperclipDistillationWorkItem(ctx, { companyId, kind: 'backfill', projectId });
Defensive patterns

Strategy: validation

Validate before calling

function assertBackfillScope(input: { projectId?: string | null; rootIssueId?: string | null }) {
  if (!input.projectId && !input.rootIssueId) {
    throw new Error('Backfill requires projectId or rootIssueId');
  }
}

Type guard

function hasBackfillScope(input: unknown): input is { projectId: string } | { rootIssueId: string } {
  return !!input && typeof input === 'object' && (
    (typeof (input as any).projectId === 'string' && (input as any).projectId.length > 0) ||
    (typeof (input as any).rootIssueId === 'string' && (input as any).rootIssueId.length > 0)
  );
}

Try / catch

try {
  await createPaperclipDistillationWorkItem(ctx, { companyId, kind: 'backfill', projectId, rootIssueId });
} catch (err) {
  if (err instanceof Error && /whole-company backfill/.test(err.message)) {
    // enumerate projects and submit one backfill each
  } else throw err;
}

Prevention

When it happens

Trigger: Calling createPaperclipDistillationWorkItem with kind:'backfill' while leaving projectId and rootIssueId null/empty, e.g. via the backfill-paperclip-distillation worker action without those params.

Common situations: Operator tries to 'backfill everything' after enabling distillation for a company; a script iterates companies but forgets to scope per project; rootIssueId was passed under the wrong param key.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/0889bb4ea59d83f1. Report an issue: GitHub.