affaan-m/ECC · error · Error
spec must be an object
Error message
spec must be an object
What it means
buildValues() validates the spec object it uses to render the agreement. It throws this error when the caller passes null, undefined, or a non-object (string, array, number) instead of a spec. The library requires an object with file/short/role fields before any rendering happens.
Solutions
- Inspect what you passed as spec — log it or JSON.stringify it before calling buildValues.
- Ensure the spec file on disk contains a JSON object ({...}), not a bare string or array.
- Add an early guard in your caller: if (!spec || typeof spec !== 'object') handle before invoking the library.
- If spec comes from JSON.parse of user input, wrap parsing in try/catch and validate shape with a schema (zod/ajv) first.
Example fix
// before
const spec = JSON.parse(fs.readFileSync(specPath, 'utf8')); // could be null/string
buildValues(spec, now);
// after
const parsed = JSON.parse(fs.readFileSync(specPath, 'utf8'));
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error(`spec file ${specPath} does not contain a JSON object`);
}
buildValues(parsed, now); Defensive patterns
Strategy: validation
Validate before calling
function isValidSpec(spec) {
return spec !== null && typeof spec === 'object' && !Array.isArray(spec);
}
if (!isValidSpec(mySpec)) throw new Error('spec must be a non-null object'); Type guard
function isSpecObject(v) {
return typeof v === 'object' && v !== null && !Array.isArray(v);
} Try / catch
try {
buildValues(spec, now);
} catch (e) {
if (e.message === 'spec must be an object') {
console.error('Spec load failed: expected a JSON object');
} else throw e;
} Prevention
- Always JSON.parse spec files inside try/catch and check the result is an object.
- Validate spec shape with a schema (zod/ajv) at the boundary before calling library functions.
- Never pass possibly-null config-loader results straight into buildValues.
When it happens
Trigger: Calling values() or buildValues() with spec = null, spec = undefined, spec = 'my-spec', spec = [], or a JSON.parse() result that turned out to be a scalar.
Common situations: A spec JSON file parsed into a non-object (e.g. the file contains just a string), a config loader returning null on missing file, or passing command-line arguments positionally in the wrong order.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- fps must be positive, got
- image_to_3d needs at least one image
- image_to_3d needs at least one image
- Invalid Claude plugin root: expected a string
- Invalid Claude target root: expected a non-empty string
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/85a76ac7ea05cc20.
Report an issue: GitHub.
Appendix: source
Thrown at skills/master-agreement-generator/scripts/build-agreement.js:81
return Array.from(rows, (row, rowIndex) => {
if (!Object.hasOwn(rows, rowIndex) || !Array.isArray(row) || row.length !== 6) {
throw new Error(`spec.schedule[${rowIndex}] must be a dense six-cell array`);
}
const cells = Array.from(row, (cell, cellIndex) => {
if (!Object.hasOwn(row, cellIndex) ||
!((typeof cell === 'string' && !/\p{Surrogate}/u.test(cell)) ||
(typeof cell === 'number' && Number.isFinite(cell)))) {
throw new Error(`spec.schedule[${rowIndex}][${cellIndex}] must be valid Unicode text or a finite number`);
}
return encodeScheduleCell(cell);
});
return `| ${cells.join(' | ')} |`;
}).join('\n');
}
function buildValues(spec, now) {
if (!spec || typeof spec !== 'object') {
throw new Error('spec must be an object');
}
for (const key of ['file', 'short', 'role']) {
if (typeof spec[key] !== 'string' || spec[key].trim() === '') {
throw new Error(`spec.${key} is required`);
}
}
// Reject path syntax on every host, including Windows paths supplied on POSIX.
if (/[<>:"/\\|?*\p{Cc}]/u.test(spec.file) ||
/[. ]$/.test(spec.file) ||
/^(con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])(?:\.|$)/i.test(spec.file)) {
throw new Error('spec.file must be a portable filename without path components or control characters');
}
const clauses = ROLE_CLAUSES[spec.role];
if (!clauses) {
throw new Error(`unknown role "${spec.role}"; expected one of ${Object.keys(ROLE_CLAUSES).join(', ')}`);
}
const cp = spec.short;
const fill = text => text.split('{cp}').join(cp);View on GitHub (pinned to 8321021c54)