{"record":{"id":"db9c76f19ba24f0c","repo":"nexu-io/open-design","slug":"filepath-must-contain-a-json-object","errorCode":null,"errorMessage":"${filePath} must contain a JSON object","messagePattern":"(.+?) must contain a JSON object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/daemon/src/artifacts-cli.ts","lineNumber":101,"sourceCode":"    } else {\n      return { error: `unknown option: ${arg}` };\n    }\n  }\n\n  return options;\n}\n\nasync function readJsonObject(filePath: string): Promise<JsonObject> {\n  const text = await readFile(filePath, 'utf8');\n  let value: unknown;\n  try {\n    value = JSON.parse(text) as unknown;\n  } catch (error) {\n    const message = error instanceof Error ? error.message : String(error);\n    throw new Error(`invalid JSON in ${filePath}: ${message}`);\n  }\n  if (!value || typeof value !== 'object' || Array.isArray(value)) {\n    throw new Error(`${filePath} must contain a JSON object`);\n  }\n  return value as JsonObject;\n}\n\nexport async function runArtifactsCli(args: string[]): Promise<ArtifactCliResult> {\n  const options = parseOptions(args);\n  if ('error' in options) return fail(options.error);\n  if (options.help || !options.command) {\n    process.stdout.write(USAGE);\n    return { exitCode: options.command ? 0 : 1 };\n  }\n  if (options.command !== 'create') return fail(`unknown artifacts command: ${options.command}`);\n  if (!options.name) return fail('create requires --name <path>');\n  if (!options.inputPath) return fail('create requires --input <file>');\n\n  try {\n    const daemonUrl = await resolveDaemonUrl(\n      options.daemonUrl === undefined ? {} : { flagUrl: options.daemonUrl },","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/artifacts-cli.ts#L83-L119","documentation":"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.","triggerScenarios":"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 `[]`.","commonSituations":"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.","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."],"exampleFix":"// before: manifest.json (array)\n[ { \"kind\": \"html\", \"renderer\": \"html\", \"exports\": [\"html\"] } ]\n\n// after: single object\n{ \"kind\": \"html\", \"renderer\": \"html\", \"exports\": [\"html\"] }","handlingStrategy":"type-guard","validationCode":"import { readFileSync } from 'node:fs';\nconst parsed = JSON.parse(readFileSync(p, 'utf8'));\nif (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n  throw new Error(`${p} must contain a JSON object`);\n}","typeGuard":"function isPlainJsonObject(value: unknown): value is Record<string, unknown> {\n  return !!value && typeof value === 'object' && !Array.isArray(value);\n}","tryCatchPattern":null,"preventionTips":["Author manifest files as a single { ... } object, never an array.","One manifest per CLI invocation; do not batch."],"tags":["cli","json","artifacts","validation","input-parsing"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}