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
- Read the appended message — it names the exact field that failed (e.g. 'artifactManifest.renderer is not allowed').
- Cross-check kind against ALLOWED_KINDS and renderer against ALLOWED_RENDERERS in apps/daemon/src/artifacts/manifest.ts.
- Ensure exports is a non-empty array of values from ALLOWED_EXPORTS (html, pdf, zip, jsx, md, svg, txt).
- 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
- Validate the manifest with validateArtifactManifestInput before submitting.
- Keep kind/renderer as a matched pair (deck -> deck-html, html -> html).
- Always supply a non-empty exports array.
- Keep supportingFiles relative and free of '..' segments.
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
- ARTIFACT_MANIFEST_REQUIRED
- ARTIFACT_PUBLICATION_BLOCKED
- sourceKind must be one of upload, url, repo, connector, arti
- ingestion body is required
- bodyMarkdown is required
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/fedaabe4e2d856ad.
Report an issue: GitHub.