nexu-io/open-design · error

proposal patch is required

Error message

proposal patch is required

What it means

Thrown by createAutomationProposal() when input.patch is falsy or not an object. patch carries the proposed change (format + after + diffSummary) and is the load-bearing field — a proposal without a patch has nothing to apply.

Source

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

}

export async function createAutomationProposal(
  dataDir: string,
  input: CreateAutomationEvolutionProposalRequest & {
    id?: string;
    status?: AutomationProposalStatus;
  },
): Promise<AutomationEvolutionProposal> {
  const now = new Date().toISOString();
  if (!input || typeof input !== 'object') throw new Error('proposal body is required');
  if (typeof input.title !== 'string' || !input.title.trim()) {
    throw new Error('proposal title is required');
  }
  if (typeof input.summary !== 'string' || !input.summary.trim()) {
    throw new Error('proposal summary is required');
  }
  if (!input.patch || typeof input.patch !== 'object') {
    throw new Error('proposal patch is required');
  }
  const status =
    input.status && VALID_STATUSES.has(input.status)
      ? input.status
      : 'pending-review';
  const proposal: AutomationEvolutionProposal = {
    id:
      typeof input.id === 'string' && input.id.trim()
        ? input.id.trim()
        : `proposal_${randomUUID()}`,
    title: input.title.trim(),
    summary: input.summary.trim(),
    targetKind: input.targetKind,
    action: input.action,
    status,
    reviewPolicy: input.reviewPolicy ?? 'always',
    createdAt: now,
    updatedAt: now,

View on GitHub (pinned to 5be4028344)

Solutions

  1. Supply patch as an object with at least format ('json' or 'markdown'), after (string), and diffSummary.
  2. If patch was serialized to a string, JSON.parse it before calling.
  3. Mirror the patch shape produced by buildMemoryProposalPatch / buildDesignSystemProposalMarkdown in automation-ingestions.ts.

Example fix

// before
await createAutomationProposal(dataDir, { title: 'T', summary: 'S', patch: '{"format":"json"}' });
// after
await createAutomationProposal(dataDir, {
  title: 'T', summary: 'S',
  patch: { format: 'json', after: '{"body":"..."}', diffSummary: 'create entry' }
});
Defensive patterns

Strategy: type-guard

Validate before calling

if (!input.patch || typeof input.patch !== 'object') {
  throw new Error('proposal patch is required');
}

Type guard

function isPatchObject(value: unknown): value is { format?: string; after?: string; diffSummary?: string } {
  return !!value && typeof value === 'object' && !Array.isArray(value);
}

Prevention

When it happens

Trigger: POST to the proposals endpoint with patch omitted, null, an array, a string, or any non-object. Fires after title and summary checks pass.

Common situations: Client sending diffSummary as the top-level field instead of nesting it under patch; caller passing a JSON string for patch instead of an object; agent emitting a proposal before serializing its patch.

Related errors


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