{"record":{"id":"5a0550557ee2df41","repo":"nexu-io/open-design","slug":"proposal-title-is-required","errorCode":null,"errorMessage":"proposal title is required","messagePattern":"proposal title is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/daemon/src/automation-proposals.ts","lineNumber":79,"sourceCode":"export async function getAutomationProposal(\n  dataDir: string,\n  id: string,\n): Promise<AutomationEvolutionProposal | null> {\n  const proposals = await listAutomationProposals(dataDir, { status: 'all' });\n  return proposals.find((proposal) => proposal.id === id) ?? null;\n}\n\nexport async function createAutomationProposal(\n  dataDir: string,\n  input: CreateAutomationEvolutionProposalRequest & {\n    id?: string;\n    status?: AutomationProposalStatus;\n  },\n): Promise<AutomationEvolutionProposal> {\n  const now = new Date().toISOString();\n  if (!input || typeof input !== 'object') throw new Error('proposal body is required');\n  if (typeof input.title !== 'string' || !input.title.trim()) {\n    throw new Error('proposal title is required');\n  }\n  if (typeof input.summary !== 'string' || !input.summary.trim()) {\n    throw new Error('proposal summary is required');\n  }\n  if (!input.patch || typeof input.patch !== 'object') {\n    throw new Error('proposal patch is required');\n  }\n  const status =\n    input.status && VALID_STATUSES.has(input.status)\n      ? input.status\n      : 'pending-review';\n  const proposal: AutomationEvolutionProposal = {\n    id:\n      typeof input.id === 'string' && input.id.trim()\n        ? input.id.trim()\n        : `proposal_${randomUUID()}`,\n    title: input.title.trim(),\n    summary: input.summary.trim(),","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/automation-proposals.ts#L61-L97","documentation":"Thrown by createAutomationProposal() when input.title is not a string or is empty/whitespace-only. title is required because it becomes the proposal's display name and, for skill/design-system targets, the fallback slug.","triggerScenarios":"POST to the proposals endpoint (or a direct call) with title omitted, empty, whitespace-only, or a non-string. The guard fires before summary and patch checks.","commonSituations":"Client form missing the title field; agent-generated proposal that left the title blank; field named differently in the client (e.g. 'name') and never mapped to title.","solutions":["Provide a non-empty title string.","Map any client-side 'name'/'label' field to title before calling.","For agent-generated proposals, derive a title from the first line of the patch body if one is not explicit."],"exampleFix":"// before\nawait createAutomationProposal(dataDir, { title: '   ', summary: 'S', patch: { format: 'json', after: '{}' } });\n// after\nawait createAutomationProposal(dataDir, { title: 'Add pricing memory', summary: 'S', patch: { format: 'json', after: '{}' } });","handlingStrategy":"validation","validationCode":"if (typeof input.title !== 'string' || !input.title.trim()) {\n  throw new Error('proposal title is required');\n}","typeGuard":"function hasNonEmptyTitle(value: unknown): value is string {\n  return typeof value === 'string' && value.trim().length > 0;\n}","tryCatchPattern":null,"preventionTips":["Map any client 'name'/'label' field to title before calling.","Derive a title from the first line of patch.after when one is not explicit.","Validate title.trim() client-side."],"tags":["automation","proposals","validation","api","daemon"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}