{"record":{"id":"44344e3497cf0c9f","repo":"nexu-io/open-design","slug":"proposal-patch-after-is-not-valid-json","errorCode":null,"errorMessage":"proposal patch.after is not valid JSON","messagePattern":"proposal patch\\.after is not valid JSON","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/daemon/src/automation-proposals.ts","lineNumber":140,"sourceCode":"  if (proposal.status === 'pending-review' || proposal.status === 'draft') return;\n  throw new Error(`proposal ${proposal.id} is ${proposal.status}, not reviewable`);\n}\n\nfunction safeMemoryType(value: unknown): MemoryType {\n  return typeof value === 'string' && VALID_MEMORY_TYPES.has(value as MemoryType)\n    ? (value as MemoryType)\n    : 'project';\n}\n\nfunction parseJsonPatchAfter(proposal: AutomationEvolutionProposal): Record<string, unknown> {\n  if (proposal.patch.format !== 'json') return {};\n  const after = proposal.patch.after;\n  if (typeof after !== 'string' || !after.trim()) return {};\n  try {\n    const parsed = JSON.parse(after);\n    return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};\n  } catch {\n    throw new Error('proposal patch.after is not valid JSON');\n  }\n}\n\nfunction withMemoryProvenance(body: string, proposal: AutomationEvolutionProposal): string {\n  const text = String(body ?? '').trimEnd();\n  const lines = text.split(/\\r?\\n/);\n  const hasProposal = lines.some((line) => /^Proposal:\\s*/i.test(line));\n  const existingPackets = new Set(\n    lines\n      .map((line) => /^Source packet:\\s*([A-Za-z0-9_-]+)\\s*$/i.exec(line)?.[1])\n      .filter((id): id is string => Boolean(id)),\n  );\n  const provenance: string[] = [];\n  for (const packetId of proposal.sourcePacketIds ?? []) {\n    if (!existingPackets.has(packetId)) provenance.push(`Source packet: ${packetId}`);\n  }\n  if (!hasProposal) provenance.push(`Proposal: ${proposal.id}`);\n  if (provenance.length === 0) return text;","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/automation-proposals.ts#L122-L158","documentation":"Thrown by parseJsonPatchAfter() when proposal.patch.format === 'json' but proposal.patch.after is not parseable as JSON. The function is called by applyMemoryProposal and applyAutomationTemplateProposal to extract structured fields from the patch body. Markdown-format patches skip parsing and never hit this.","triggerScenarios":"Applying a memory-node or automation-template proposal whose patch.format is 'json' but patch.after contains malformed JSON (trailing comma, unquoted key, truncated string). Also triggered if after was hand-edited after proposal creation.","commonSituations":"Agent emitted patch.after with a trailing comma or single quotes; serialized JSON was truncated in storage; manual edit of proposals.json broke the after payload; template upsert payload was built by string concatenation.","solutions":["Open the proposal record and validate patch.after with a JSON parser; fix the syntax at the reported position.","If the after payload is irreparable, reject the proposal and create a new one with a correctly serialized patch.","When building json-format proposals programmatically, always use JSON.stringify rather than template literals."],"exampleFix":"// before — hand-built JSON\npatch: { format: 'json', after: '{ name: \"x\", }' }\n// after — JSON.stringify\npatch: { format: 'json', after: JSON.stringify({ name: 'x' }) }","handlingStrategy":"validation","validationCode":"if (proposal.patch.format === 'json') {\n  JSON.parse(proposal.patch.after); // throws if invalid\n}","typeGuard":"function isValidJsonAfter(after: unknown): boolean {\n  if (typeof after !== 'string') return false;\n  try { JSON.parse(after); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  await applyAutomationProposal(dataDir, id);\n} catch (e) {\n  if (/patch\\.after is not valid JSON/.test((e as Error).message)) {\n    // reject the proposal and recreate with a JSON.stringify'd patch\n  }\n}","preventionTips":["Always build json-format patch.after with JSON.stringify.","Validate patch.after parses before persisting the proposal.","Avoid hand-editing proposals.json after creation."],"tags":["automation","proposals","json","validation","daemon"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}