{"record":{"id":"de69c54374508eac","repo":"can1357/oh-my-pi","slug":"generated-identifier-is-invalid-must-be-lowercase","errorCode":null,"errorMessage":"Generated identifier is invalid (must be lowercase kebab-case, 2+ words)","messagePattern":"Generated identifier is invalid \\(must be lowercase kebab-case, 2\\+ words\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/modes/components/agents-hub.ts","lineNumber":188,"sourceCode":"}\n\nfunction parseGeneratedAgentSpec(raw: string): GeneratedAgentSpec {\n\tconst parsed = JSON.parse(extractJsonObject(raw)) as Partial<GeneratedAgentSpec>;\n\tif (!parsed || typeof parsed !== \"object\") {\n\t\tthrow new Error(\"Model output is not a JSON object\");\n\t}\n\tif (\n\t\ttypeof parsed.identifier !== \"string\" ||\n\t\ttypeof parsed.whenToUse !== \"string\" ||\n\t\ttypeof parsed.systemPrompt !== \"string\"\n\t) {\n\t\tthrow new Error(\"Model output is missing required fields (identifier, whenToUse, systemPrompt)\");\n\t}\n\tconst identifier = parsed.identifier.trim();\n\tconst whenToUse = parsed.whenToUse.trim();\n\tconst systemPrompt = parsed.systemPrompt.trim();\n\tif (!IDENTIFIER_PATTERN.test(identifier)) {\n\t\tthrow new Error(\"Generated identifier is invalid (must be lowercase kebab-case, 2+ words)\");\n\t}\n\tif (!whenToUse.toLowerCase().startsWith(\"use this agent when\")) {\n\t\tthrow new Error(\"Generated whenToUse must start with 'Use this agent when...'\");\n\t}\n\tif (!systemPrompt) {\n\t\tthrow new Error(\"Generated systemPrompt is empty\");\n\t}\n\treturn { identifier, whenToUse, systemPrompt };\n}\n\nfunction matchAgent(agent: HubAgent, query: string): boolean {\n\tconst text = `${agent.name} ${agent.description} ${SOURCE_LABEL[agent.source]} ${agent.overrideModel ?? \"\"}`;\n\treturn query\n\t\t.trim()\n\t\t.split(/\\s+/)\n\t\t.every(token => fuzzyMatch(token, text).matches);\n}\n","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/modes/components/agents-hub.ts#L170-L206","documentation":"parseGeneratedAgentSpec validates the trimmed identifier against IDENTIFIER_PATTERN, which requires lowercase kebab-case with 2+ words (at least one hyphen). An identifier like 'Reviewer', 'reviewer', or 'x' fails the pattern and throws this error. It keeps generated agent ids consistent with the filesystem/config naming conventions used for agents.","triggerScenarios":"#runAgentCreationArchitect receives a valid spec whose identifier violates the kebab-case/2-word rule — e.g. 'CodeReviewer' (uppercase), 'reviewer' (single word), 'code_reviewer' (underscore), or 'code--reviewer' style malformed ids.","commonSituations":"Models producing CamelCase or single-word names from the user's feature description; non-English descriptions leading to a single-token identifier; model echoing the user's verbatim agent name.","solutions":["Retry the generation; if it repeats, state the naming constraint (lowercase kebab-case, two or more words) explicitly to the architect/user prompt.","Sanitize/derive the identifier yourself from the description before invoking creation, and pass it as guidance.","If validating locally before persisting, apply the same IDENTIFIER_PATTERN check to catch bad ids early."],"exampleFix":"// before (model output)\n{\"identifier\":\"CodeReviewer\", ...}\n// after\n{\"identifier\":\"code-reviewer\", ...}","handlingStrategy":"validation","validationCode":"const IDENTIFIER_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)+$/;\nfunction identifierOk(id: string): boolean {\n  return IDENTIFIER_PATTERN.test(id.trim());\n}\nif (!identifierOk(parsed.identifier)) parsed.identifier = slugify(parsed.description);","typeGuard":"function isKebabCaseMultiWord(v: unknown): v is string {\n  return typeof v === \"string\" && /^[a-z0-9]+(-[a-z0-9]+)+$/.test(v);\n}","tryCatchPattern":"try {\n  const spec = parseGeneratedAgentSpec(raw);\n} catch (err) {\n  if (err.message.startsWith(\"Generated identifier is invalid\")) {\n    const fixed = { ...parsed(raw), identifier: slugify(parsed(raw).identifier) };\n    return parseGeneratedAgentSpec(JSON.stringify(fixed));\n  } throw err;\n}","preventionTips":["State the kebab-case, 2+ word rule verbatim in the generation prompt with an example.","Pre-normalize identifiers (slugify) before submitting specs for validation.","Share the IDENTIFIER_PATTERN regex in client-side pre-validation."],"tags":["llm-output","naming","validation","agents"],"backgroundTag":"invalid-identifier-format","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}