paperclipai/paperclip · error · Error
projectId or rootIssueId is required
Error message
projectId or rootIssueId is required
What it means
Thrown by distillPaperclipProjectPage when neither input.projectId nor input.rootIssueId is provided. The function renders a project wiki page (index.md/standup.md) and needs a concrete scope to name the slug and directory. The guard fires before policy evaluation so the request fails fast.
Source
Thrown at packages/plugins/plugin-llm-wiki/src/wiki/core.ts:3338
export function getDistillationAutoApplyRestriction(): DistillationAutoApplyRestriction {
const rawMode = process.env.PAPERCLIP_DEPLOYMENT_MODE;
const rawExposure = process.env.PAPERCLIP_DEPLOYMENT_EXPOSURE;
const deploymentMode =
rawMode === "local_trusted" || rawMode === "authenticated" ? rawMode : null;
const deploymentExposure =
rawExposure === "private" || rawExposure === "public" ? rawExposure : null;
const blocked = deploymentMode === "authenticated" && deploymentExposure === "public";
return {
autoApplyAllowed: !blocked,
autoApplyRestriction: blocked ? PUBLIC_DISTILLATION_AUTO_APPLY_RESTRICTION : null,
deploymentMode,
deploymentExposure,
};
}
export async function distillPaperclipProjectPage(ctx: PluginContext, input: PaperclipProjectPageDistillationInput) {
if (!input.projectId && !input.rootIssueId) {
throw new Error("projectId or rootIssueId is required");
}
const wikiId = normalizeWikiId(input.wikiId);
assertPaperclipSourceScopePayload(input);
const space = await requirePaperclipIngestionPolicy(ctx, { companyId: input.companyId, wikiId, spaceSlug: input.spaceSlug }, "execute", { requireEnabledProfile: true });
const scope = paperclipCursorScopeMetadata(input);
const issues = await listPaperclipBundleIssues(ctx, input);
const project = scope.projectId ? await ctx.projects.get(scope.projectId, input.companyId) : null;
const rootIssue = scope.rootIssueId ? await ctx.issues.get(scope.rootIssueId, input.companyId) : null;
const slug = projectPageSlug({ project, rootIssue });
const projectDir = `wiki/projects/${slug}`;
const standupPath = assertPagePath(`${projectDir}/standup.md`);
const pagePath = assertPagePath(`${projectDir}/index.md`);
const run = await createPaperclipDistillationRun(ctx, input);
const bundle = run.bundle;
const current = await readCurrentWithHash(ctx, input.companyId, pagePath, space);
assertExpectedHash(input.expectedProjectPageHash, current.hash, pagePath);
if (!hasDurableSignal(bundle, issues)) {View on GitHub (pinned to 67001ec6eb)
Solutions
- Pass projectId (preferred) or rootIssueId on every distillPaperclipProjectPage input.
- For distillation triggered from an issue, resolve the issue's projectId from ctx.issues.get and forward it.
- Validate the scope at the caller (worker action) before invoking the core function so users get a clearer upstream error.
Example fix
// before
distillPaperclipProjectPage(ctx, { companyId, wikiId, spaceSlug });
// after
distillPaperclipProjectPage(ctx, { companyId, wikiId, spaceSlug, projectId }); Defensive patterns
Strategy: validation
Validate before calling
function assertProjectPageScope(input: { projectId?: string | null; rootIssueId?: string | null }) {
if (!input.projectId && !input.rootIssueId) throw new Error('projectId or rootIssueId is required');
} Type guard
function hasProjectPageScope(input: unknown): input is { projectId: string } | { rootIssueId: string } {
return !!input && typeof input === 'object' && (
!!(input as any).projectId || !!(input as any).rootIssueId
);
} Try / catch
try {
await distillPaperclipProjectPage(ctx, input);
} catch (err) {
if (err instanceof Error && err.message === 'projectId or rootIssueId is required') {
// resolve projectId from the issue and retry
} else throw err;
} Prevention
- Resolve projectId from the triggering issue before distillation.
- Forward scope params through every worker action that calls distillPaperclipProjectPage.
When it happens
Trigger: Calling distillPaperclipProjectPage with both projectId and rootIssueId absent or empty, e.g. a distill worker action that did not forward the scope params.
Common situations: A worker action param mapping drops projectId; an issue-driven distillation run where the source issue is not attached to a project and has no root-issue lineage; misconfigured routine passing only companyId.
Related errors
- Paperclip ingestion fan-out exceeds the hard cap of ${MAX_PA
- Backfill work items must target a projectId or rootIssueId;
- 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/227fa6e6091ec101.
Report an issue: GitHub.