nexu-io/open-design · error
${filePath} must contain a JSON object
Error message
${filePath} must contain a JSON object What it means
Thrown by readJsonObject() after a successful JSON.parse when the parsed value is not a JSON object — i.e. it is an array, string, number, boolean, or null. The artifacts CLI --manifest flag must carry an object (the manifest fields), so any top-level non-object is rejected.
Source
Thrown at apps/daemon/src/artifacts-cli.ts:101
} else {
return { error: `unknown option: ${arg}` };
}
}
return options;
}
async function readJsonObject(filePath: string): Promise<JsonObject> {
const text = await readFile(filePath, 'utf8');
let value: unknown;
try {
value = JSON.parse(text) as unknown;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`invalid JSON in ${filePath}: ${message}`);
}
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`${filePath} must contain a JSON object`);
}
return value as JsonObject;
}
export async function runArtifactsCli(args: string[]): Promise<ArtifactCliResult> {
const options = parseOptions(args);
if ('error' in options) return fail(options.error);
if (options.help || !options.command) {
process.stdout.write(USAGE);
return { exitCode: options.command ? 0 : 1 };
}
if (options.command !== 'create') return fail(`unknown artifacts command: ${options.command}`);
if (!options.name) return fail('create requires --name <path>');
if (!options.inputPath) return fail('create requires --input <file>');
try {
const daemonUrl = await resolveDaemonUrl(
options.daemonUrl === undefined ? {} : { flagUrl: options.daemonUrl },View on GitHub (pinned to 5be4028344)
Solutions
- Ensure the manifest file's top-level value is a single JSON object delimited by { }.
- If you have multiple manifests, pick one per invocation — the CLI takes one artifact at a time.
- Run `node -e "const v=JSON.parse(require('fs').readFileSync('<path>','utf8')); console.log(typeof v, Array.isArray(v))"` to confirm it parses to a non-array object.
Example fix
// before: manifest.json (array)
[ { "kind": "html", "renderer": "html", "exports": ["html"] } ]
// after: single object
{ "kind": "html", "renderer": "html", "exports": ["html"] } Defensive patterns
Strategy: type-guard
Validate before calling
import { readFileSync } from 'node:fs';
const parsed = JSON.parse(readFileSync(p, 'utf8'));
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error(`${p} must contain a JSON object`);
} Type guard
function isPlainJsonObject(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === 'object' && !Array.isArray(value);
} Prevention
- Author manifest files as a single { ... } object, never an array.
- One manifest per CLI invocation; do not batch.
When it happens
Trigger: Passing --manifest a file whose contents are a JSON array `[...]`, a bare string `"..."`, a number, or `null`. Also triggered by a file that contains only `[]`.
Common situations: Author confused a manifest object with a list of manifests; exported a JSON array from a tool; truncated a file down to a scalar; passed a package.json-style top-level array by mistake.
Related errors
- invalid JSON in ${filePath}: ${message}
- ARTIFACT_MANIFEST_INVALID
- ARTIFACT_MANIFEST_REQUIRED
- ARTIFACT_PUBLICATION_BLOCKED
- proposal patch.after is not valid JSON
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/db9c76f19ba24f0c.
Report an issue: GitHub.