{"record":{"id":"06b6aa25611c44d8","repo":"can1357/oh-my-pi","slug":"invalid-plugin-name-name","errorCode":null,"errorMessage":"Invalid plugin name: \"${name}\"","messagePattern":"Invalid plugin name: \"(.+?)\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/extensibility/plugins/marketplace/types.ts","lineNumber":26,"sourceCode":" * The installed registry MUST pass `parseClaudePluginsRegistry()` validation —\n * it uses `version: 2` (numeric) and `plugins: Record<string, ...[]>`.\n */\n\n// ── Plugin ID helpers ────────────────────────────────────────────────\n\nconst NAME_RE = /^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/;\nconst MAX_NAME_LENGTH = 64;\nconst MAX_ID_LENGTH = 128;\n\n/** Validate a plugin or marketplace name segment. */\nexport function isValidNameSegment(s: string): boolean {\n\treturn s.length > 0 && s.length <= MAX_NAME_LENGTH && NAME_RE.test(s);\n}\n\n/** Build canonical plugin ID: `\"name@marketplace\"`. Both segments are validated. */\nexport function buildPluginId(name: string, marketplace: string): string {\n\tif (!isValidNameSegment(name)) {\n\t\tthrow new Error(`Invalid plugin name: \"${name}\"`);\n\t}\n\tif (!isValidNameSegment(marketplace)) {\n\t\tthrow new Error(`Invalid marketplace name: \"${marketplace}\"`);\n\t}\n\tconst id = `${name}@${marketplace}`;\n\tif (id.length > MAX_ID_LENGTH) {\n\t\tthrow new Error(`Plugin ID exceeds ${MAX_ID_LENGTH} characters: \"${id}\"`);\n\t}\n\treturn id;\n}\n\n/** Parse `\"name@marketplace\"` → `{ name, marketplace }` or `null`. */\nexport function parsePluginId(id: string): { name: string; marketplace: string } | null {\n\tconst atIndex = id.lastIndexOf(\"@\");\n\tif (atIndex <= 0 || atIndex === id.length - 1) return null;\n\n\tconst name = id.slice(0, atIndex);\n\tconst marketplace = id.slice(atIndex + 1);","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/extensibility/plugins/marketplace/types.ts#L8-L44","documentation":"buildPluginId constructs canonical plugin IDs of the form `\"name@marketplace\"` and validates both segments with isValidNameSegment: 1–64 chars, lowercase alphanumerics with internal dots and hyphens only (must start and end with [a-z0-9]). This throw means the plugin name segment failed that pattern — empty, too long, uppercase, or containing disallowed characters such as `@`, `_`, spaces, or a leading/trailing dot or hyphen.","triggerScenarios":"Calling buildPluginId(name, marketplace) (or its wrappers pluginId/id/id1/id2) with a name that is empty, longer than 64 characters, contains uppercase letters, underscores, `@`, spaces, slashes, or begins/ends with `.` or `-` — typically when generating an ID from an untrusted marketplace.json plugin entry.","commonSituations":"A marketplace catalog lists a plugin named `My_Plugin` or `my-tool-` (trailing hyphen); code builds an ID from a user-typed plugin name instead of the validated slug; automation derives names from GitHub repo names containing uppercase or underscores; empty name after a failed split/trim.","solutions":["Normalize the name to a valid slug before building the ID: lowercase, replace invalid characters (`[^a-z0-9.-]`) with `-`, strip leading/trailing dots/hyphens.","Fix the plugin's `name` in the marketplace.json source catalog to match /^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/ and ≤64 chars.","If the name came from a registry/config file, re-derive it from parsePluginId on the canonical ID rather than from a display label.","Validate with isValidNameSegment(name) before calling buildPluginId and surface a friendly message instead of the raw throw."],"exampleFix":"// before\nconst id = buildPluginId(rawName, market); // rawName = \"My_Tool\"\n// after\nconst slug = rawName.toLowerCase().replace(/[^a-z0-9.-]+/g, \"-\").replace(/^[.-]+|[.-]+$/g, \"\");\nconst id = buildPluginId(slug, market);","handlingStrategy":"validation","validationCode":"import { isValidNameSegment } from \"@oh-my-pi/pi-coding-agent/.../marketplace/types\";\nif (!isValidNameSegment(name)) {\n\tthrow new Error(`Plugin name must match /^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/ and be 1-64 chars: \"${name}\"`);\n}","typeGuard":"function isPluginName(v: unknown): v is string {\n\treturn typeof v === \"string\" && /^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/.test(v) && v.length <= 64;\n}","tryCatchPattern":"let id: string;\ntry {\n\tid = buildPluginId(name, marketplace);\n} catch (err) {\n\tif (err instanceof Error && err.message.startsWith('Invalid plugin name')) {\n\t\tid = buildPluginId(slugify(name), marketplace);\n\t} else throw err;\n}","preventionTips":["Slugify any externally sourced name (lowercase, `[^a-z0-9.-]` → `-`, trim edge dots/hyphens) before building IDs.","Never build IDs from display labels or user-typed strings without validation.","Run isValidNameSegment on both segments in a precondition check so failures are attributed correctly.","Keep plugin `name` fields in marketplace.json lowercase-kebab from the start."],"tags":["validation","naming","marketplace","plugin-id"],"backgroundTag":"invalid-name-segment","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}