nexu-io/open-design · error · ArtifactManifestInvalidError

ARTIFACT_MANIFEST_INVALID

ARTIFACT_MANIFEST_INVALID

Error message

invalid artifactManifest: ${message}

What it means

Thrown by resolveCreateArtifactManifest() as ArtifactManifestInvalidError (code ARTIFACT_MANIFEST_INVALID) when an explicit artifactManifest was supplied but validateArtifactManifestInput() rejected it. The appended message is the specific validation failure (kind/renderer/exports/status/path constraint). The validation rules live in artifacts/manifest.ts.

Source

Thrown at apps/daemon/src/artifacts/create.ts:62

export function buildCreateArtifactRequestBody(input: CreateProjectArtifactInput): JsonObject {
  return {
    name: input.name,
    content: input.content,
    encoding: input.encoding === 'base64' ? 'base64' : 'utf8',
    artifact: true,
    overwrite: false,
    ...(input.artifactManifest === undefined ? {} : { artifactManifest: input.artifactManifest }),
  };
}

export function resolveCreateArtifactManifest(input: CreateProjectArtifactInput): unknown {
  const manifest = input.artifactManifest !== undefined && input.artifactManifest !== null
    ? input.artifactManifest
    : inferLegacyManifest(input.name);
  if (manifest) {
    const validated = validateArtifactManifestInput(manifest, input.name);
    if (!validated.ok) {
      throw new ArtifactManifestInvalidError(validated.error);
    }
    return validated.value;
  }
  throw new ArtifactManifestRequiredError(input.name);
}

export async function createProjectArtifactFile(options: CreateProjectArtifactOptions): Promise<unknown> {
  const { input } = options;
  const body = input.encoding === 'base64'
    ? Buffer.from(input.content, 'base64')
    : Buffer.from(input.content, 'utf8');
  return await options.writeProjectFile(
    options.projectsRoot,
    options.projectId,
    input.name,
    body,
    {
      overwrite: false,

View on GitHub (pinned to 5be4028344)

Solutions

  1. Read the appended message — it names the exact field that failed (e.g. 'artifactManifest.renderer is not allowed').
  2. Cross-check kind against ALLOWED_KINDS and renderer against ALLOWED_RENDERERS in apps/daemon/src/artifacts/manifest.ts.
  3. Ensure exports is a non-empty array of values from ALLOWED_EXPORTS (html, pdf, zip, jsx, md, svg, txt).
  4. Strip absolute paths, '..' segments, and null bytes from supportingFiles; keep metadata under 16KB.

Example fix

// before
{
  "artifactManifest": { "kind": "pitch", "renderer": "slides", "exports": [] }
}
// after — allowed kind/renderer, non-empty exports
{
  "artifactManifest": {
    "kind": "deck",
    "renderer": "deck-html",
    "exports": ["html", "pdf", "zip"]
  }
}
Defensive patterns

Strategy: validation

Validate before calling

import { validateArtifactManifestInput } from './artifacts/manifest.js';
const result = validateArtifactManifestInput(manifest, name);
if (!result.ok) throw new Error(`manifest invalid: ${result.error}`);
// safe to submit
postCreateArtifactRequest({ baseUrl, projectId, input: { name, content, encoding, artifactManifest: result.value } });

Type guard

const ALLOWED_KINDS = new Set(['html','deck','react-component','markdown-document','svg','diagram','code-snippet','mini-app','design-system']);
const ALLOWED_RENDERERS = new Set(['html','deck-html','react-component','markdown','svg','diagram','code','mini-app','design-system']);
const ALLOWED_EXPORTS = new Set(['html','pdf','zip','jsx','md','svg','txt']);
function isValidManifestShape(m: unknown): m is { kind: string; renderer: string; exports: string[] } {
  return !!m && typeof m === 'object'
    && typeof (m as any).kind === 'string' && ALLOWED_KINDS.has((m as any).kind)
    && typeof (m as any).renderer === 'string' && ALLOWED_RENDERERS.has((m as any).renderer)
    && Array.isArray((m as any).exports) && (m as any).exports.every((e: string) => ALLOWED_EXPORTS.has(e));
}

Try / catch

try {
  await createProjectArtifactFile(options);
} catch (e) {
  if (e instanceof ArtifactManifestInvalidError) {
    // surface validated.error to the user; do not retry unchanged
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /api/projects/:id/files with artifact=true and an artifactManifest whose kind is not in ALLOWED_KINDS, renderer not in ALLOWED_RENDERERS, exports empty or containing unsupported values, status not streaming|complete|error, supportingFiles with absolute/traversal paths, or metadata exceeding 16KB. Also fired by `od artifacts create --manifest` because it forwards to the same resolver.

Common situations: Mismatched kind/renderer pair (e.g. kind 'deck' with renderer 'html' instead of 'deck-html'); forgetting exports is required and non-empty; passing a relative path that contains '..' in supportingFiles; metadata blob grown past 16KB.

Related errors


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