nexu-io/open-design · error

bodyMarkdown is required

Error message

bodyMarkdown is required

What it means

Thrown by ingestAutomationSource() after sourceKind is validated, when bodyMarkdown is not a non-empty string after trimming. bodyMarkdown is the actual content being ingested, so an empty value leaves nothing to packetize or propose.

Source

Thrown at apps/daemon/src/automation-ingestions.ts:374

  next.push(packet);
  await writePackets(dataDir, next);
}

function jsonObjectFrom(value: unknown): Record<string, JsonValue> {
  if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
  return value as Record<string, JsonValue>;
}

export async function ingestAutomationSource(
  dataDir: string,
  input: CreateAutomationSourceIngestionRequest,
): Promise<AutomationSourceIngestionResponse> {
  if (!input || typeof input !== 'object') {
    throw new Error('ingestion body is required');
  }
  const sourceKind = sourceKindFrom(input.sourceKind);
  const bodyMarkdown = typeof input.bodyMarkdown === 'string' ? input.bodyMarkdown.trim() : '';
  if (!bodyMarkdown) throw new Error('bodyMarkdown is required');

  const template = input.templateId ? await getAnyAutomationTemplate(dataDir, input.templateId) : null;
  const templateSinks = template?.outputSinks ?? ['memory'];
  const candidateSinks = outputSinksFrom(input.candidateSinks, templateSinks);
  const reviewPolicy = reviewPolicyFrom(input.reviewPolicy, template?.reviewPolicy ?? 'always');
  const tokenCompression = compressionModeFrom(
    input.tokenCompression,
    template?.tokenCompression ?? 'balanced',
  );
  const packetId = `packet_${randomUUID()}`;
  const sourceEventId = `source_event_${randomUUID()}`;
  const capturedAt = new Date().toISOString();
  const sourceRef =
    optionalString(input.sourceRef) ??
    optionalString(input.connectorId) ??
    optionalString(input.artifactId) ??
    optionalString(input.conversationId) ??
    sourceKind;

View on GitHub (pinned to 5be4028344)

Solutions

  1. Supply a non-empty bodyMarkdown string with the real content to ingest.
  2. If content is genuinely empty, skip the ingestion call rather than sending an empty body.
  3. Validate on the client side that bodyMarkdown.trim() is non-empty before posting.

Example fix

// before
await ingestAutomationSource(dataDir, { sourceKind: 'upload', bodyMarkdown: '' });
// after
await ingestAutomationSource(dataDir, { sourceKind: 'upload', bodyMarkdown: '# Notes\n\nReal content here.' });
Defensive patterns

Strategy: validation

Validate before calling

if (typeof input.bodyMarkdown !== 'string' || !input.bodyMarkdown.trim()) {
  throw new Error('bodyMarkdown is required');
}

Type guard

function hasNonEmptyBodyMarkdown(value: unknown): value is string {
  return typeof value === 'string' && value.trim().length > 0;
}

Prevention

When it happens

Trigger: POST to the ingestion endpoint with bodyMarkdown omitted, set to an empty string, whitespace-only, or a non-string type. The check runs after sourceKind, so a bad sourceKind masks this error.

Common situations: Client attaching metadata but forgetting the content payload; form submitted before the user typed anything; upstream extractor returned an empty string for the page content.

Related errors


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