paperclipai/paperclip · error · Error

: missing local branding provenance

Error message

${slug}: missing local branding provenance

What it means

brandingFor() resolves each app slug to a locally-hosted brand asset via ui/public/brands/apps/manifest.json. If the slug is not in the manifest and is not one of the two generic fallbacks (oauth-generic/api-key-generic), ingestion refuses to emit an AppDefinition without provenance, because a published app must ship a verifiable local logo rather than a remote or missing one.

Solutions

  1. Add (or fix the slug of) the provider entry in ui/public/brands/apps/manifest.json, including localAsset pointing at the bundled SVG under /brands/apps/.
  2. Verify the app slug in scripts/ingest-app-definitions.mjs matches the manifest slug exactly (case-sensitive), including darkAsset handling.
  3. Regenerate the branding manifest via the asset-export pipeline if a new provider asset was added but not ingested.
  4. If the app is intentionally generic (no real provider brand), use slug "oauth-generic" or "api-key-generic", which have hardcoded fallbacks.

Example fix

// before
slug: "slackapp"
// after (manifest.json providers entry must exist)
slug: "slack" // entry: { "slug": "slack", "localAsset": "/brands/apps/slack.svg" }
Defensive patterns

Strategy: validation

Validate before calling

const manifest = JSON.parse(fs.readFileSync('ui/public/brands/apps/manifest.json','utf8'));
const slugs = new Set(manifest.providers.map(p => p.slug));
if (!slugs.has(slug) && !['oauth-generic','api-key-generic'].includes(slug)) {
  throw new Error(`${slug}: add branding manifest entry before ingesting`);
}

Type guard

const hasBranding = (slug) => typeof slug === 'string' && (genericSlugs.includes(slug) || brandingBySlug.has(slug));

Prevention

When it happens

Trigger: Calling brandingFor(slug) (directly or via app-definition generation in scripts/ingest-app-definitions.mjs) with a slug that (a) has no entry in brandingBySlug (built from ui/public/brands/apps/manifest.json 'providers' keyed by 'slug'), and (b) is not exactly "oauth-generic" or "api-key-generic".

Common situations: Adding a new app definition without first exporting its brand asset and regenerating the branding manifest; a slug typo or rename in the app definition that no longer matches the manifest slug; manually editing manifest.json and dropping a provider entry; running the ingest script in a checkout where ui/public/brands/apps/manifest.json is stale or truncated.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/41d53824af90babf. Report an issue: GitHub.

Appendix: source

Thrown at scripts/ingest-app-definitions.mjs:32

const brandingManifest = JSON.parse(
  fs.readFileSync(
    path.join(root, "ui/public/brands/apps/manifest.json"),
    "utf8",
  ),
);
const brandingBySlug = new Map(
  brandingManifest.providers.map((entry) => [entry.slug, entry]),
);
const brandingFor = (slug) => {
  const entry = brandingBySlug.get(slug);
  if (entry)
    return {
      logoUrl: entry.localAsset,
      ...(entry.darkAsset ? { darkLogoUrl: entry.darkAsset } : {}),
    };
  if (slug === "oauth-generic" || slug === "api-key-generic")
    return { logoUrl: `/brands/apps/${slug}.svg` };
  throw new Error(`${slug}: missing local branding provenance`);
};
const field = (key, label, placeholder) => ({
  key,
  label,
  type: "password",
  required: true,
  placeholder,
  secret: true,
});
const method = (
  key,
  transport,
  auth,
  defaults,
  riskTier,
  guidanceMd,
  extra = {},
) => ({

View on GitHub (pinned to 3f1d897a7c)