affaan-m/ECC · error

Invalid at : expected a JSON object

Error message

Invalid ${label} at ${filePath}: expected a JSON object

What it means

After successful JSON.parse, readJsonObject() enforces that the top-level value is a non-null, non-array object. Arrays, null, strings, or numbers in a file that must hold a settings/metadata object throw 'Invalid <label> at <filePath>: expected a JSON object'. The hooks configuration model is keyed by event names, so only objects are acceptable at the root.

Solutions

  1. Rewrite the file so its root is an object, e.g. { "hooks": { ... } }.
  2. If your data is currently an array, wrap it: { "hooks": { "Stop": [ ...entries ] } }.
  3. If the file should be empty, use {} rather than [] or null.

Example fix

// before (settings.json)
[ { "type": "command", "command": "node hook.js" } ]
// after
{ "hooks": { "Stop": [ { "hooks": [ { "type": "command", "command": "node hook.js" } ] } ] } }
Defensive patterns

Strategy: type-guard

Validate before calling

const parsed = JSON.parse(fs.readFileSync(path, 'utf8'));
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
  throw new Error(`${path} must contain a JSON object at the root`);
}

Type guard

function isPlainObject(v) { return v !== null && typeof v === 'object' && !Array.isArray(v); }

Try / catch

try {
  const cfg = readHooksConfig(path);
} catch (err) {
  if (String(err.message).includes('expected a JSON object')) {
    console.error(`${path} has a non-object root; wrapping or resetting it.`);
    const v = JSON.parse(fs.readFileSync(path, 'utf8'));
    fs.writeFileSync(path, JSON.stringify(Array.isArray(v) ? { hooks: { Stop: v } } : {}, null, 2));
  } else throw err;
}

Prevention

When it happens

Trigger: Calling hooksConfig/readHooksConfig/addJsonMergeOperation against a file containing a JSON array (e.g. [] or [{...}]), null, a bare string/number, or true — syntactically valid JSON but the wrong root type.

Common situations: Someone replaced settings.json contents with an array of hooks; a tool exported the config as a list; a reset script wrote null; copy-paste put a JSON snippet rather than an object into the file.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/c64cb162f457e80f. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/hooks-config.js:43

const FINGERPRINT_PATTERN = /^[0-9a-f]{12}$/;

function readJsonObject(filePath, label) {
  let raw;
  try {
    raw = fs.readFileSync(filePath, 'utf8');
  } catch (error) {
    throw new Error(`Unable to read ${label} at ${filePath}: ${error.message}`);
  }

  let parsed;
  try {
    parsed = JSON.parse(raw);
  } catch (error) {
    throw new Error(`Invalid JSON in ${label} at ${filePath}: ${error.message}`);
  }

  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
    throw new Error(`Invalid ${label} at ${filePath}: expected a JSON object`);
  }

  return parsed;
}

function metadataPathFor(hooksPath) {
  return path.join(path.dirname(hooksPath), METADATA_FILENAME);
}

/**
 * JSON.stringify with object keys sorted, so a fingerprint does not change when
 * someone reorders the keys inside a hook object.
 */
function stableStringify(value) {
  if (Array.isArray(value)) {
    return `[${value.map(stableStringify).join(',')}]`;
  }
  if (value && typeof value === 'object') {

View on GitHub (pinned to 8321021c54)