vercel-labs/agent-skills · error · Error
${label} must be a JSON object.
Error message
${label} must be a JSON object. What it means
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.
Source
Thrown at skills/vercel-optimize/scripts/merge-signals.mjs:166
else if (a === '--force') out.force = true;
else out.positional.push(a);
}
out.signalsPath = out.positional[0];
out.codebasePath = out.positional[1];
return out;
}
async function readJson(path, label) {
try {
return JSON.parse(await readFile(path, 'utf-8'));
} catch (err) {
throw new Error(`Could not read ${label} JSON at ${path}: ${err.message}`);
}
}
function assertObject(value, label) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`${label} must be a JSON object.`);
}
}
async function writeOutput(path, body, { force }) {
if (!force && await exists(path)) {
throw new Error(`output file already exists: ${path}. Use a fresh run directory or pass --force to overwrite.`);
}
await mkdir(dirname(path), { recursive: true });
await writeFile(path, body);
}
async function exists(path) {
try {
await access(path);
return true;
} catch {
return false;
}View on GitHub (pinned to b8caa260a4)
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 `[`.
Example fix
// before — codebase.json is a bare array
[ { "route": "/" } ]
// after — wrapped as scan-codebase output
{ "stack": {...}, "routes": [...], "findings": [...] } Defensive patterns
Strategy: type-guard
Validate before calling
function assertPlainObject(value, label) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`${label} must be a JSON object, got ${Array.isArray(value) ? 'array' : typeof value}`);
}
}
assertPlainObject(parsed, 'signals'); Type guard
function isPlainJsonObject(v) {
return v !== null && typeof v === 'object' && !Array.isArray(v);
} Prevention
- 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.
When it happens
Trigger: 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 `{...}`.
Common situations: 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.
Related errors
- signals.json is missing schemaVersion; pass collect-signals
- codebase.json must be scan-codebase output with stack, route
- VERCEL_VERSION_UNPARSEABLE
- Could not read ${label} JSON at ${path}: ${err.message}
- VERCEL_NOT_INSTALLED
AI-assisted analysis of vercel-labs/agent-skills@b8caa260a4 (2026-08-13).
Data as JSON: /api/errors/ec2d7d20854d3727.
Report an issue: GitHub.