nexu-io/open-design · error · Error

artifactManifest must be an object

Error message

artifactManifest must be an object

What it means

Thrown in the artifact-creation MCP handler before POSTing to the artifact endpoint. `artifactManifest` is an optional structured manifest describing the artifact; if present it must be a plain object so downstream artifact rendering can read named fields. null, arrays, and primitives are rejected to fail fast rather than produce a malformed artifact.

Source

Thrown at apps/daemon/src/mcp.ts:2854

    return null;
  }
}

async function createArtifact(
  baseUrl: string,
  args: McpArgs,
  headers?: Record<string, string>,
) {
  const { id, resolved, active } = await resolveProjectArg(baseUrl, args.project, headers);
  requireString(args.name, 'name');
  requireString(args.content, 'content');
  if (
    args.artifactManifest !== undefined &&
    (args.artifactManifest === null ||
      typeof args.artifactManifest !== 'object' ||
      Array.isArray(args.artifactManifest))
  ) {
    throw new Error('artifactManifest must be an object');
  }
  const artifactManifest =
    args.artifactManifest
      ? args.artifactManifest
      : undefined;
  const payload = await postCreateArtifactRequest({
    baseUrl,
    projectId: id,
    ...(headers ? { headers } : {}),
    input: {
      name: args.name,
      content: args.content,
      encoding: args.encoding === 'base64' ? 'base64' : 'utf8',
      ...(artifactManifest === undefined ? {} : { artifactManifest }),
    },
  });
  const result = payload && typeof payload === 'object' && !Array.isArray(payload)
    ? (payload as JsonObject)

View on GitHub (pinned to 5be4028344)

Solutions

  1. Pass a plain object: `artifactManifest: { ... }`, or omit it to use the default.
  2. Parse serialized manifests: `artifactManifest: JSON.parse(raw)`.
  3. If you built an array of manifests, pick the single intended entry.
  4. Verify the manifest field names against the artifact route's expected schema in packages/contracts.

Example fix

// before
{ name, content, artifactManifest: null }
// after
{ name, content }  // omit when default manifest is fine
Defensive patterns

Strategy: type-guard

Validate before calling

if (artifactManifest !== undefined && (artifactManifest === null || typeof artifactManifest !== 'object' || Array.isArray(artifactManifest))) {
  throw new TypeError('artifactManifest must be a plain object');
}

Type guard

function isManifest(v: unknown): v is Record<string, unknown> {
  return typeof v === 'object' && v !== null && !Array.isArray(v);
}

Prevention

When it happens

Trigger: Calling the create-artifact MCP tool with `artifactManifest: [...]`, `artifactManifest: null`, or a JSON string. Happens when a caller copies a list of manifest entries into the field, or forwards a serialized manifest without parsing.

Common situations: LLM emits an array because the schema is ambiguous; manifest imported from a JSON file left as a string; passing the whole request envelope instead of its manifest sub-object.

Related errors


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