affaan-m/ECC · error
Invalid : expected a non-empty JSON object
Error message
Invalid ${label}: expected a non-empty JSON object What it means
validateRecordedManagedHooks validates the recorded (persisted) managed-hooks snapshot, which must be a non-empty plain JSON object. It throws this when the value is not a JSON object (null, array, string, etc.) or is an empty object. This check runs before the looser per-event checks; it guards the ECC bookkeeping that records which hooks it previously installed, so a missing/corrupt snapshot is surfaced loudly rather than silently treated as 'no previous hooks'.
Solutions
- Ensure the recorded hooks value is a non-empty plain object keyed by event, e.g. { PreToolUse: [{ id, hooks: [...] }] }.
- Check the source file/state store for a missing or emptied recorded-hooks key and restore it (or re-run the install so it is re-recorded).
- Fix the JSON parse path that yields null for empty/corrupt files before passing it in.
- If there truly are no previous hooks, pass null/omit the value through the API's 'no previous hooks' path rather than an empty object, if supported.
Example fix
// before
const recorded = JSON.parse(fs.readFileSync(stateFile, 'utf8')) ?? {}; // {} throws
// after
const parsed = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && Object.keys(parsed).length > 0) {
validateRecordedManagedHooks(parsed);
}
Defensive patterns
Strategy: validation
Validate before calling
function canValidateRecordedHooks(v) {
return v !== null && typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length > 0;
}
if (recorded !== undefined && !canValidateRecordedHooks(recorded)) {
throw new Error('Recorded hooks snapshot missing or empty — re-run install to re-record');
} Type guard
function isNonEmptyHooksRecord(v) {
return v !== null && typeof v === 'object' && !Array.isArray(v)
&& Object.getPrototypeOf(v) !== Object.getPrototypeOf(new Map())
&& Object.keys(v).length > 0;
} Try / catch
try {
const prev = findPreviousManagedHooks(state);
} catch (err) {
if (/expected a non-empty JSON object/.test(err.message)) {
console.error('Recorded hooks snapshot is missing/empty — the state file may be corrupt; re-run install');
}
throw err;
} Prevention
- Guard JSON.parse results for null before storing the snapshot
- Never initialize recorded-hooks state with {} — use null/undefined for 'no previous hooks'
- Back up or validate the state file before migrations that rename keys
- Re-record the snapshot via a fresh install if the store is corrupted
When it happens
Trigger: Calling validate, findPreviousManagedHooks, previousHooks, or recordedHooks with recorded hooks read as null/undefined (missing field in the store), an empty object {}, a JSON array, or a string blob.
Common situations: A settings/state file truncated or hand-edited so the recorded hooks key was deleted; JSON parse returning null for an empty file; a schema migration that renamed the key, leaving the old lookup undefined; initializing state with {} as a placeholder.
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 Claude hooks preference
- Invalid hook entry at
- INVALID_HOOK_MODE
- Invalid hooks config: expected a JSON object with a hooks…
- Invalid install-state
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/e5d867128d2b030e.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/install/claude-settings.js:196
);
}
if (!Array.isArray(entry.hooks) || entry.hooks.length === 0) {
throw new Error(
`Invalid managed hook entry at ${label}.${event}[${index}]: expected hooks`
);
}
entry.hooks.forEach((hook, hookIndex) => {
validateHookHandler(hook, `${label}.${event}[${index}].hooks[${hookIndex}]`);
});
});
}
return cloneValue(managedHooks);
}
function validateRecordedManagedHooks(managedHooks, label = 'recorded managed hooks') {
if (!isJsonObject(managedHooks) || Object.keys(managedHooks).length === 0) {
throw new Error(`Invalid ${label}: expected a non-empty JSON object`);
}
for (const [event, entries] of Object.entries(managedHooks)) {
if (!isNonEmptyString(event) || !Array.isArray(entries) || entries.length === 0) {
throw new Error(`Invalid ${label}.${event}: expected a non-empty hook array`);
}
const seenIds = new Set();
entries.forEach((entry, index) => {
if (!isJsonObject(entry) || !isNonEmptyString(entry.id) || !Array.isArray(entry.hooks)) {
throw new Error(`Invalid hook entry at ${label}.${event}[${index}]`);
}
if (seenIds.has(entry.id)) {
throw new Error(
`Invalid ${label}: expected unique id "${entry.id}" within event "${event}"`
);
}
seenIds.add(entry.id);
});
}View on GitHub (pinned to 8321021c54)