{"record":{"id":"c8b00fb8ab3e61fd","repo":"nexu-io/open-design","slug":"automation-template-id-must-be-a-safe-slug","errorCode":null,"errorMessage":"automation template id must be a safe slug","messagePattern":"automation template id must be a safe slug","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/daemon/src/automation-templates.ts","lineNumber":237,"sourceCode":"function cleanReviewPolicy(value: unknown): AutomationReviewPolicy {\n  return typeof value === 'string' && REVIEW_POLICIES.has(value as AutomationReviewPolicy)\n    ? value as AutomationReviewPolicy\n    : 'always';\n}\n\nfunction cleanCompressionMode(value: unknown): AutomationTokenCompressionMode {\n  return typeof value === 'string' && COMPRESSION_MODES.has(value as AutomationTokenCompressionMode)\n    ? value as AutomationTokenCompressionMode\n    : 'balanced';\n}\n\nexport function normalizeAutomationTemplate(input: unknown): AutomationTemplate {\n  if (!input || typeof input !== 'object' || Array.isArray(input)) {\n    throw new Error('automation template must be an object');\n  }\n  const raw = input as Record<string, unknown>;\n  const id = cleanId(raw.id);\n  if (!id) throw new Error('automation template id must be a safe slug');\n  const rawStages = Array.isArray(raw.stages) ? raw.stages : [];\n  const stages = rawStages\n    .map((stage): AutomationTemplate['stages'][number] | null => {\n      if (!stage || typeof stage !== 'object' || Array.isArray(stage)) return null;\n      const stageRaw = stage as Record<string, unknown>;\n      const stageId = cleanId(stageRaw.id);\n      const kind = stageRaw.kind;\n      const title = stageRaw.title;\n      if (!stageId || typeof title !== 'string' || !title.trim()) return null;\n      if (typeof kind !== 'string' || !STAGE_KINDS.has(kind as AutomationTemplateStageKind)) {\n        return null;\n      }\n      return {\n        id: stageId,\n        kind: kind as AutomationTemplateStageKind,\n        title: title.trim(),\n        ...(typeof stageRaw.description === 'string' && stageRaw.description.trim()\n          ? { description: stageRaw.description.trim() }","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/automation-templates.ts#L219-L255","documentation":"Thrown when cleanId(raw.id) returns empty, i.e. raw.id fails SAFE_ID = /^[a-z0-9][a-z0-9._-]{1,95}$/. The id must be lowercase, start with an alphanumeric character, be 2-96 characters long, and contain only [a-z0-9._-]. Uppercase, spaces, slashes, leading punctuation, and over-length ids are rejected.","triggerScenarios":"Submitting a template whose id contains uppercase letters, spaces, or slashes (e.g. 'My Template' or 'foo/bar'); an id with a leading underscore or dot ('.foo', '_bar'); an id longer than 96 characters; a missing id field.","commonSituations":"Using a human title as the id; slugify producing Title Case or leaving spaces; auto-generated ids that include UUIDs with uppercase hex (UUIDs are lowercase hex+dash and pass, but any uppercase variant fails); copy-pasting an id with a trailing newline.","solutions":["Slugify the id to lowercase kebab-case (e.g. 'my-template-v2').","Ensure the first character is a letter or digit and total length is between 2 and 96.","Strip spaces, slashes, and uppercase before submitting; only [a-z0-9._-] are allowed after the first char."],"exampleFix":"// before\nawait upsertUserAutomationTemplate(dataDir, { id: 'My Template!', /* ... */ });\n\n// after\nawait upsertUserAutomationTemplate(dataDir, { id: 'my-template', /* ... */ });","handlingStrategy":"validation","validationCode":"const SAFE_ID = /^[a-z0-9][a-z0-9._-]{1,95}$/;\nfunction isValidTemplateId(id: unknown): id is string {\n  return typeof id === 'string' && SAFE_ID.test(id);\n}\nif (!isValidTemplateId(payload.id)) {\n  throw new Error('id must be lowercase, 2-96 chars, start alphanumeric, only [a-z0-9._-]');\n}","typeGuard":"const SAFE_ID = /^[a-z0-9][a-z0-9._-]{1,95}$/;\nfunction isSafeTemplateId(id: unknown): id is string {\n  return typeof id === 'string' && SAFE_ID.test(id);\n}","tryCatchPattern":"try {\n  await upsertUserAutomationTemplate(dataDir, payload);\n} catch (err) {\n  if (err instanceof Error && err.message === 'automation template id must be a safe slug') {\n    return badRequest('Template id must be a lowercase slug (2-96 chars, [a-z0-9._-]).');\n  }\n  throw err;\n}","preventionTips":["Always slugify user-entered ids to lowercase kebab-case.","Cap id length at 96 characters and require an alphanumeric first character.","Run SAFE_ID.test(id) on the client before submitting."],"tags":["automation","validation","slug","templates"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}