{"record":{"id":"ec2d7d20854d3727","repo":"vercel-labs/agent-skills","slug":"label-must-be-a-json-object","errorCode":null,"errorMessage":"${label} must be a JSON object.","messagePattern":"(.+?) must be a JSON object\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"skills/vercel-optimize/scripts/merge-signals.mjs","lineNumber":166,"sourceCode":"    else if (a === '--force') out.force = true;\n    else out.positional.push(a);\n  }\n  out.signalsPath = out.positional[0];\n  out.codebasePath = out.positional[1];\n  return out;\n}\n\nasync function readJson(path, label) {\n  try {\n    return JSON.parse(await readFile(path, 'utf-8'));\n  } catch (err) {\n    throw new Error(`Could not read ${label} JSON at ${path}: ${err.message}`);\n  }\n}\n\nfunction assertObject(value, label) {\n  if (!value || typeof value !== 'object' || Array.isArray(value)) {\n    throw new Error(`${label} must be a JSON object.`);\n  }\n}\n\nasync function writeOutput(path, body, { force }) {\n  if (!force && await exists(path)) {\n    throw new Error(`output file already exists: ${path}. Use a fresh run directory or pass --force to overwrite.`);\n  }\n  await mkdir(dirname(path), { recursive: true });\n  await writeFile(path, body);\n}\n\nasync function exists(path) {\n  try {\n    await access(path);\n    return true;\n  } catch {\n    return false;\n  }","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/vercel-labs/agent-skills/blob/b8caa260a420a73042e35521de4b5c8baf6446cc/skills/vercel-optimize/scripts/merge-signals.mjs#L148-L184","documentation":"Thrown by assertObject() when the parsed JSON value is not a plain object — i.e. it is null, a primitive, or an array (Array.isArray check). mergeSignals requires both inputs to be objects before reading fields like schemaVersion/routes, so a top-level array or scalar is rejected. This catches cases where a file contains a JSON array of findings instead of the wrapper object.","triggerScenarios":"A signals.json whose top level is an array; a codebase.json that is `null` or a bare string/number; a file containing only `[...]` (e.g. raw findings list) instead of `{...}`.","commonSituations":"A scan/collect step wrote a bare array instead of the wrapper object; an empty file parsed as null; someone hand-edited the JSON down to a list.","solutions":["Re-run the producing step so it emits the full object wrapper, not a bare array.","If you have a bare array, wrap it: `node -e 'const a=require(\"./x.json\");require(\"fs\").writeFileSync(\"x.json\",JSON.stringify({findings:a},null,2))'` only if the schema expects it.","Inspect the file's first non-whitespace character — `{` is required, not `[`."],"exampleFix":"// before — codebase.json is a bare array\n[ { \"route\": \"/\" } ]\n\n// after — wrapped as scan-codebase output\n{ \"stack\": {...}, \"routes\": [...], \"findings\": [...] }","handlingStrategy":"type-guard","validationCode":"function assertPlainObject(value, label) {\n  if (!value || typeof value !== 'object' || Array.isArray(value)) {\n    throw new Error(`${label} must be a JSON object, got ${Array.isArray(value) ? 'array' : typeof value}`);\n  }\n}\nassertPlainObject(parsed, 'signals');","typeGuard":"function isPlainJsonObject(v) {\n  return v !== null && typeof v === 'object' && !Array.isArray(v);\n}","tryCatchPattern":null,"preventionTips":["Ensure producer steps emit object wrappers, not bare arrays.","Inspect the first character of JSON files — expect '{', not '['.","Validate parsed shape with isPlainJsonObject before field access."],"tags":["validation","parsing","data-contract","vercel"],"backgroundTag":null,"analyzedSha":"b8caa260a420a73042e35521de4b5c8baf6446cc","analyzedAt":"2026-08-13T06:03:29.508Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}