{"record":{"id":"de0ef3252b53a4e8","repo":"paperclipai/paperclip","slug":"skill-reference-is-required","errorCode":null,"errorMessage":"Skill reference is required.","messagePattern":"Skill reference is required\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cli/src/commands/client/skills.ts","lineNumber":611,"sourceCode":"async function getCatalogSkill(ctx: ResolvedClientContext, catalogRef: string): Promise<CatalogSkill> {\n  const ref = catalogRef.trim();\n  if (!ref) {\n    throw new Error(\"Catalog skill reference is required.\");\n  }\n  const detail = await ctx.api.get<CatalogSkill>(`/api/skills/catalog/ref?ref=${encodeURIComponent(ref)}`);\n  if (!detail) {\n    throw new Error(`Catalog skill not found: ${catalogRef}`);\n  }\n  return detail;\n}\n\nexport function resolveCompanySkillReference(\n  skills: CompanySkillReferenceTarget[],\n  reference: string,\n): CompanySkillReferenceTarget {\n  const trimmed = reference.trim();\n  if (!trimmed) {\n    throw new Error(\"Skill reference is required.\");\n  }\n\n  const byId = skills.find((skill) => skill.id === trimmed);\n  if (byId) return byId;\n\n  const byKey = skills.find((skill) => skill.key === trimmed);\n  if (byKey) return byKey;\n\n  const normalizedSlug = normalizeSkillSlug(trimmed);\n  const bySlug = skills.filter((skill) => skill.slug === normalizedSlug);\n  if (bySlug.length === 1 && bySlug[0]) return bySlug[0];\n  if (bySlug.length > 1) {\n    throw new Error(`Ambiguous skill slug \"${trimmed}\". Use a skill ID or key instead.`);\n  }\n\n  throw new Error(`Skill not found: ${reference}`);\n}\n","sourceCodeStart":593,"sourceCodeEnd":629,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/cli/src/commands/client/skills.ts#L593-L629","documentation":"Thrown by resolveCompanySkillReference() when the reference string is empty after trimming. This shared resolver backs every company-skill operation (show, files, update, reset, remove, audit, agent sync). It guards up front because all downstream lookups (by id, key, slug) would otherwise be no-ops.","triggerScenarios":"Any company-scoped skills command with a blank positional `<skillRef>`: `paperclipai skills show \"\"`, `skills remove \"  \"`, or an agent sync where the resolver is invoked with empty input.","commonSituations":"Unset environment variable expanded into the positional arg, programmatic callers forgetting to supply a ref, or trailing whitespace from a list-parse step.","solutions":["Supply a non-empty company skill ID, key, or unique slug as the positional argument.","If resolving dynamically, list company skills first (`skills list`) and feed a concrete id/key.","Trim and validate the value in your wrapper script before calling the CLI."],"exampleFix":"// before\nawait run([\"skills\", \"show\", refFromUser]);\n// after\nconst ref = (refFromUser ?? \"\").trim();\nif (!ref) throw new Error(\"skillRef is required\");\nawait run([\"skills\", \"show\", ref]);","handlingStrategy":"validation","validationCode":"const ref = (skillRef ?? \"\").trim();\nif (!ref) {\n  throw new Error(\"skillRef is required for this command\");\n}","typeGuard":"function isNonEmptyRef(ref: string | undefined): ref is string {\n  return typeof ref === \"string\" && ref.trim().length > 0;\n}","tryCatchPattern":null,"preventionTips":["Validate positional args in your wrapper before dispatching.","Never pass unchecked user input straight to skills subcommands.","Use the same trim-and-check helper across all skills commands."],"tags":["cli","validation","skills","input"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}