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
- Rewrite the file so its root is an object, e.g. { "hooks": { ... } }.
- If your data is currently an array, wrap it: { "hooks": { "Stop": [ ...entries ] } }.
- 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
- Always start hooks config files with { } at the root; never a bare array or null.
- Export tooling should serialize the whole config object, not just the entries list.
- Add a startup assertion that config roots are plain objects.
- Use {} as the canonical 'empty config' content.
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
- Invalid at : expected a JSON object
- Invalid at : expected a JSON object
- Invalid at : expected a JSON object
- Invalid : expected JSON-compatible data
- Invalid legacy Codex sync state at
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)