{"record":{"id":"96d054e04c845d22","repo":"nexu-io/open-design","slug":"automation-template-key-is-required","errorCode":null,"errorMessage":"automation template ${key} is required","messagePattern":"automation template (.+?) is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/daemon/src/automation-templates.ts","lineNumber":188,"sourceCode":"  'off',\n  'balanced',\n  'aggressive',\n]);\n\nfunction storePath(dataDir: string): string {\n  return path.join(dataDir, STORE_DIR, STORE_FILE);\n}\n\nfunction cleanId(value: unknown): string {\n  if (typeof value !== 'string') return '';\n  const id = value.trim();\n  return SAFE_ID.test(id) ? id : '';\n}\n\nfunction requiredString(input: Record<string, unknown>, key: string): string {\n  const value = input[key];\n  if (typeof value !== 'string' || !value.trim()) {\n    throw new Error(`automation template ${key} is required`);\n  }\n  return value.trim();\n}\n\nfunction cleanStringArray(value: unknown): string[] {\n  if (!Array.isArray(value)) return [];\n  const out: string[] = [];\n  for (const item of value) {\n    if (typeof item !== 'string') continue;\n    const trimmed = item.trim();\n    if (trimmed && !out.includes(trimmed)) out.push(trimmed);\n  }\n  return out;\n}\n\nfunction cleanEnumArray<T extends string>(\n  value: unknown,\n  allowed: ReadonlySet<T>,","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/automation-templates.ts#L170-L206","documentation":"Thrown by the requiredString() helper inside normalizeAutomationTemplate. It enforces that the title, description, and purpose fields on a user automation template are present, non-empty strings. The ${key} placeholder names the offending field so the caller knows exactly which one is missing.","triggerScenarios":"Calling upsertUserAutomationTemplate(dataDir, input) (or normalizeAutomationTemplate(input)) where input.title, input.description, or input.purpose is omitted, set to an empty string, whitespace-only, or a non-string type.","commonSituations":"Importing an exported template JSON that omitted purpose; a UI form that does not require all three fields; a YAML-to-JSON conversion that dropped a blank field; passing a partial patch object instead of a full template.","solutions":["Add the field named in ${key} as a non-empty trimmed string to the template payload.","Run the payload through a schema/validator (Zod or a JSON schema) before calling upsertUserAutomationTemplate so all required keys are checked up front.","If any field is intentionally blank in your UI, default it to a sensible human-readable sentence instead of submitting an empty string."],"exampleFix":"// before\nawait upsertUserAutomationTemplate(dataDir, {\n  id: 'my-template',\n  title: 'My Template',\n  stages: [{ id: 'ingest', kind: 'ingest', title: 'Capture' }],\n  // description and purpose missing -> throws \"automation template description is required\"\n});\n\n// after\nawait upsertUserAutomationTemplate(dataDir, {\n  id: 'my-template',\n  title: 'My Template',\n  description: 'One-line summary of what this template does.',\n  purpose: 'Why this template exists and when to use it.',\n  stages: [{ id: 'ingest', kind: 'ingest', title: 'Capture' }],\n});","handlingStrategy":"validation","validationCode":"const REQUIRED_TEMPLATE_FIELDS = ['title', 'description', 'purpose'] as const;\nfunction validateRequiredTemplateFields(input: unknown): string | null {\n  if (!input || typeof input !== 'object') return 'template must be an object';\n  const obj = input as Record<string, unknown>;\n  for (const key of REQUIRED_TEMPLATE_FIELDS) {\n    const v = obj[key];\n    if (typeof v !== 'string' || !v.trim()) return key;\n  }\n  return null;\n}\n// before upsert:\nconst missing = validateRequiredTemplateFields(payload);\nif (missing) throw new Error(`Missing required field: ${missing}`);","typeGuard":"function isAutomationTemplateInput(v: unknown): v is Record<string, unknown> & { title: string; description: string; purpose: string } {\n  if (!v || typeof v !== 'object' || Array.isArray(v)) return false;\n  const o = v as Record<string, unknown>;\n  return ['title', 'description', 'purpose'].every(k => typeof o[k] === 'string' && (o[k] as string).trim().length > 0);\n}","tryCatchPattern":"try {\n  await upsertUserAutomationTemplate(dataDir, payload);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('automation template ') && err.message.endsWith(' is required')) {\n    // surface the missing field name back to the user/UI form\n    return badRequest(err.message);\n  }\n  throw err;\n}","preventionTips":["Always include title, description, and purpose in template payloads — all three are mandatory.","Validate with a schema (Zod or JSON schema) at the route boundary before reaching the daemon module.","Treat whitespace-only strings as missing; the helper trims before checking."],"tags":["automation","validation","daemon","templates"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}