paperclipai/paperclip · error · Error
Paperclip source scope must specify either projectId or root
Error message
Paperclip source scope must specify either projectId or rootIssueId, not both.
What it means
Thrown by assertPaperclipSourceScopePayload() when a Paperclip source scope payload specifies BOTH projectId and rootIssueId. A source scope is exclusive: it scopes either to a single project (selected_projects) or to a set of root issues (root_issues), never both. The guard rejects ambiguous payloads before they are normalized.
Source
Thrown at packages/plugins/plugin-llm-wiki/src/wiki/core.ts:481
const space = await resolveSpace(ctx, {
companyId: input.companyId,
wikiId: input.wikiId,
spaceSlug: input.spaceSlug,
});
const profile = await profileForSpace(ctx, input.companyId, space);
const decision = evaluatePaperclipProfilePolicy({
space,
profile,
purpose,
requireEnabledProfile: options.requireEnabledProfile,
});
if (!decision.allowed) throw new Error(decision.message);
return decision.space;
}
function assertPaperclipSourceScopePayload(input: { projectId?: string | null; rootIssueId?: string | null }) {
if (input.projectId && input.rootIssueId) {
throw new Error("Paperclip source scope must specify either projectId or rootIssueId, not both.");
}
}
function assertRequestedCharacterLimit(name: string, value: unknown, max: number) {
if (value == null) return;
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
throw new Error(`${name} must be a positive number.`);
}
if (Math.floor(value) > max) {
throw new Error(`${name} exceeds the hard Paperclip ingestion cap of ${max} characters.`);
}
}
function stableSpaceId(input: { companyId: string; wikiId: string; slug: string }): string {
const hex = createHash("md5")
.update(`${input.companyId}:${input.wikiId}:${input.slug}`)
.digest("hex");
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;View on GitHub (pinned to 67001ec6eb)
Solutions
- Set exactly one of projectId or rootIssueId on the scope payload — leave the other null/undefined.
- In the UI, make the project and root-issue selectors mutually exclusive.
- Validate the payload with a schema that uses oneOf/exclusiveProperties before sending.
Example fix
// before
const scope = { projectId: "p1", rootIssueId: "i1" }; // throws
// after
const scope = { projectId: "p1" }; // project-scoped Defensive patterns
Strategy: validation
Validate before calling
function assertScopePayload(p: { projectId?: string | null; rootIssueId?: string | null }) {
if (p.projectId && p.rootIssueId) {
throw new Error("Specify either projectId or rootIssueId, not both.");
}
} Type guard
function isExclusiveScopePayload(p: { projectId?: string | null; rootIssueId?: string | null }): boolean {
return !(p.projectId && p.rootIssueId);
} Prevention
- Make projectId and rootIssueId mutually exclusive in the UI.
- Validate with a oneOf schema at the API boundary.
- Initialize unused scope field to null, never to an empty string that could be truthy.
When it happens
Trigger: Submitting a source scope payload with both fields populated (e.g. { projectId: "p1", rootIssueId: "i1" }). API client that builds the payload by merging two partial configs.
Common situations: Frontend form allowing both selectors to be filled. Migration code that unioned fields from two legacy shapes. Confused API consumer assuming both are required.
Related errors
- ${name} must be a positive number.
- ${name} exceeds the hard Paperclip ingestion cap of ${max} c
- Paperclip ingestion profile must include at least one source
- Everything in the company is only available on the default w
- Paperclip skills must be directories.
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/8aac09a586212071.
Report an issue: GitHub.