paperclipai/paperclip · error · Error
Project belongs to another company or does not exist: ${proj
Error message
Project belongs to another company or does not exist: ${projectId} What it means
Thrown during profile validation when ctx.projects.get(projectId, companyId) returns nullish for a project id listed in a 'selected_projects' scope. The projects.get helper is company-scoped, so a null result means the id is unknown or belongs to a different company. This guards ingestion from binding to projects outside the company boundary.
Source
Thrown at packages/plugins/plugin-llm-wiki/src/wiki/core.ts:1007
});
if (!policy.allowed) throw new Error(policy.message);
if (input.profile.enabled && input.profile.sourceScopes.length === 0) {
throw new Error("Paperclip ingestion profile must include at least one source scope before it can be enabled.");
}
if (input.profile.sourceScopes.length > MAX_PAPERCLIP_INGESTION_PROFILE_SOURCE_COUNT) {
throw new Error(`Paperclip ingestion profile sources exceed the hard cap of ${MAX_PAPERCLIP_INGESTION_PROFILE_SOURCE_COUNT}.`);
}
for (const scope of input.profile.sourceScopes) {
if (scope.kind === "company_all" && input.space.slug !== DEFAULT_SPACE_SLUG) {
throw new Error("Everything in the company is only available on the default wiki space.");
}
if (scope.kind === "selected_projects") {
if (scope.projectIds.length > MAX_PAPERCLIP_PROFILE_SELECTED_PROJECTS) {
throw new Error(`selected_projects exceeds the hard cap of ${MAX_PAPERCLIP_PROFILE_SELECTED_PROJECTS}.`);
}
for (const projectId of scope.projectIds) {
const project = await ctx.projects.get(projectId, input.companyId);
if (!project) throw new Error(`Project belongs to another company or does not exist: ${projectId}`);
}
}
if (scope.kind === "root_issues") {
if (scope.issueIds.length > MAX_PAPERCLIP_PROFILE_ROOT_ISSUES) {
throw new Error(`root_issues exceeds the hard cap of ${MAX_PAPERCLIP_PROFILE_ROOT_ISSUES}.`);
}
for (const issueId of scope.issueIds) {
const issue = await ctx.issues.get(issueId, input.companyId);
if (!issue) throw new Error(`Issue belongs to another company or does not exist: ${issueId}`);
}
}
}
}
export async function updatePaperclipIngestionProfile(ctx: PluginContext, input: {
companyId: string;
wikiId?: string | null;
spaceSlug?: string | null;View on GitHub (pinned to 67001ec6eb)
Solutions
- Call listPaperclipIngestionCandidates first and restrict the picker to ids it returns for the current company.
- Strip project ids that no longer exist before submitting, or surface them to the user for re-selection.
- Verify the companyId passed into the profile update matches the company that owns every projectId.
- If a project was just created, wait for it to be readable before adding it to the scope.
Example fix
// before
profile.sourceScopes = [{ kind: "selected_projects", projectIds: rawProjectIds }];
// after
const candidates = await listPaperclipIngestionCandidates(ctx, { companyId, wikiId });
const valid = new Set(candidates.projects.map((p) => p.id));
profile.sourceScopes = [{
kind: "selected_projects",
projectIds: rawProjectIds.filter((id) => valid.has(id)),
}]; Defensive patterns
Strategy: validation
Validate before calling
async function filterExistingProjects(ctx, companyId, projectIds) {
const candidates = await listPaperclipIngestionCandidates(ctx, { companyId, wikiId: DEFAULT_WIKI_ID });
const valid = new Set(candidates.projects.map((p) => p.id));
return projectIds.filter((id) => valid.has(id));
} Type guard
function isLikelyProjectId(id) {
return typeof id === "string" && id.length > 0 && /^[a-zA-Z0-9_-]+$/.test(id);
} Try / catch
try {
await updatePaperclipIngestionProfile(ctx, { companyId, spaceSlug, profile });
} catch (err) {
if (/Project belongs to another company or does not exist/.test(err.message)) {
const id = err.message.split(": ").pop();
profile.sourceScopes = profile.sourceScopes.map((s) =>
s.kind === "selected_projects" ? { ...s, projectIds: s.projectIds.filter((p) => p !== id) } : s);
return updatePaperclipIngestionProfile(ctx, { companyId, spaceSlug, profile });
}
throw err;
} Prevention
- Refresh candidates before saving the profile.
- Scope project lookups to the current company.
- Drop archived/deleted ids from cached pickers.
When it happens
Trigger: Submitting a 'selected_projects' scope whose projectIds includes an id that was deleted, mis-typed, copied from another company, or pending creation. The lookup happens inside validatePaperclipIngestionProfile, reached via updatePaperclipIngestionProfile.
Common situations: Stale project id cached in a UI form after the project was archived/deleted; cross-company copy-paste of ids; test fixtures referencing a project from a different tenant; race between project creation and profile save.
Related errors
- Issue belongs to another company or does not exist: ${issueI
- selected_projects exceeds the hard cap of ${MAX_PAPERCLIP_PR
- root_issues exceeds the hard cap of ${MAX_PAPERCLIP_PROFILE_
- Selected Wiki Maintainer agent was not found or is terminate
- Selected LLM Wiki project was not found.
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/e1bebe1f570201f4.
Report an issue: GitHub.