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

  1. Inspect what you passed as spec — log it or JSON.stringify it before calling buildValues.
  2. Ensure the spec file on disk contains a JSON object ({...}), not a bare string or array.
  3. Add an early guard in your caller: if (!spec || typeof spec !== 'object') handle before invoking the library.
  4. 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

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


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)