nexu-io/open-design · error

proposal patch.after is not valid JSON

Error message

proposal patch.after is not valid JSON

What it means

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.

Source

Thrown at apps/daemon/src/automation-proposals.ts:140

  if (proposal.status === 'pending-review' || proposal.status === 'draft') return;
  throw new Error(`proposal ${proposal.id} is ${proposal.status}, not reviewable`);
}

function safeMemoryType(value: unknown): MemoryType {
  return typeof value === 'string' && VALID_MEMORY_TYPES.has(value as MemoryType)
    ? (value as MemoryType)
    : 'project';
}

function parseJsonPatchAfter(proposal: AutomationEvolutionProposal): Record<string, unknown> {
  if (proposal.patch.format !== 'json') return {};
  const after = proposal.patch.after;
  if (typeof after !== 'string' || !after.trim()) return {};
  try {
    const parsed = JSON.parse(after);
    return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
  } catch {
    throw new Error('proposal patch.after is not valid JSON');
  }
}

function withMemoryProvenance(body: string, proposal: AutomationEvolutionProposal): string {
  const text = String(body ?? '').trimEnd();
  const lines = text.split(/\r?\n/);
  const hasProposal = lines.some((line) => /^Proposal:\s*/i.test(line));
  const existingPackets = new Set(
    lines
      .map((line) => /^Source packet:\s*([A-Za-z0-9_-]+)\s*$/i.exec(line)?.[1])
      .filter((id): id is string => Boolean(id)),
  );
  const provenance: string[] = [];
  for (const packetId of proposal.sourcePacketIds ?? []) {
    if (!existingPackets.has(packetId)) provenance.push(`Source packet: ${packetId}`);
  }
  if (!hasProposal) provenance.push(`Proposal: ${proposal.id}`);
  if (provenance.length === 0) return text;

View on GitHub (pinned to 5be4028344)

Solutions

  1. Open the proposal record and validate patch.after with a JSON parser; fix the syntax at the reported position.
  2. If the after payload is irreparable, reject the proposal and create a new one with a correctly serialized patch.
  3. When building json-format proposals programmatically, always use JSON.stringify rather than template literals.

Example fix

// before — hand-built JSON
patch: { format: 'json', after: '{ name: "x", }' }
// after — JSON.stringify
patch: { format: 'json', after: JSON.stringify({ name: 'x' }) }
Defensive patterns

Strategy: validation

Validate before calling

if (proposal.patch.format === 'json') {
  JSON.parse(proposal.patch.after); // throws if invalid
}

Type guard

function isValidJsonAfter(after: unknown): boolean {
  if (typeof after !== 'string') return false;
  try { JSON.parse(after); return true; } catch { return false; }
}

Try / catch

try {
  await applyAutomationProposal(dataDir, id);
} catch (e) {
  if (/patch\.after is not valid JSON/.test((e as Error).message)) {
    // reject the proposal and recreate with a JSON.stringify'd patch
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/44344e3497cf0c9f. Report an issue: GitHub.