paperclipai/paperclip · error · Error

projectId or rootIssueId is required

Error message

projectId or rootIssueId is required

What it means

Thrown by the backfill-paperclip-distillation worker action when neither params.projectId nor params.rootIssueId is provided. This is the action-layer mirror of error 501; backfill must be scoped. The guard fires before createPaperclipDistillationWorkItem is called.

Source

Thrown at packages/plugins/plugin-llm-wiki/src/worker.ts:641

      });
      return { status: "queued", workItem, operation };
    });

    ctx.actions.register("enable-paperclip-distillation-active-projects", async (params) => {
      return enableActiveProjectDistillation(ctx, {
        companyId: readCompanyIdFromParams(params),
        wikiId: stringField(params.wikiId),
        spaceSlug: stringField(params.spaceSlug),
        limit: typeof params.limit === "number" ? params.limit : null,
      });
    });

    ctx.actions.register("backfill-paperclip-distillation", async (params) => {
      const companyId = readCompanyIdFromParams(params);
      const spaceSlug = stringField(params.spaceSlug);
      const projectId = stringField(params.projectId);
      const rootIssueId = stringField(params.rootIssueId);
      if (!projectId && !rootIssueId) throw new Error("projectId or rootIssueId is required");
      const backfillStartAt = stringField(params.backfillStartAt);
      const backfillEndAt = stringField(params.backfillEndAt);
      const idempotencyScope = rootIssueId ? `root:${rootIssueId}` : `project:${projectId}`;
      const workItem = await createPaperclipDistillationWorkItem(ctx, {
        companyId,
        wikiId: stringField(params.wikiId),
        spaceSlug,
        kind: "backfill",
        projectId,
        rootIssueId,
        requestedByIssueId: stringField(params.requestedByIssueId),
        priority: "low",
        idempotencyKey: stringField(params.idempotencyKey) ?? `backfill:${idempotencyScope}:${backfillStartAt ?? "begin"}:${backfillEndAt ?? "now"}`,
        metadata: { backfillStartAt, backfillEndAt, requestedFrom: "backfill-paperclip-distillation" },
      });
      const operation = await createOperationIssue(ctx, {
        companyId,
        wikiId: stringField(params.wikiId),

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Pass projectId or rootIssueId on the backfill action.
  2. For company-wide coverage, loop over projects and call the action once per projectId.
  3. Use camelCase param names (projectId, rootIssueId).

Example fix

// before
ctx.actions.run('backfill-paperclip-distillation', { companyId });
// after
ctx.actions.run('backfill-paperclip-distillation', { companyId, projectId });
Defensive patterns

Strategy: validation

Validate before calling

function assertBackfillScope(params: { projectId?: string | null; rootIssueId?: string | null }) {
  if (!params.projectId && !params.rootIssueId) throw new Error('projectId or rootIssueId is required');
}

Type guard

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

Try / catch

try { await worker.actions['backfill-paperclip-distillation'](params); } catch (err) { if (/projectId or rootIssueId is required/.test(String(err))) { /* loop projects */ } else throw err; }

Prevention

When it happens

Trigger: Invoking the backfill-paperclip-distillation action with projectId and rootIssueId both null/blank.

Common situations: Operator requests a company-wide backfill; script enumerates companies but not projects; projectId was passed as 'project_id'.

Related errors


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