affaan-m/ECC · error
Invalid : expected a JSON object
Error message
Invalid ${label}: expected a JSON object What it means
validateManagedHooks expects the managed hooks config itself to be a plain JSON object mapping hook event names to entry arrays. This error is thrown when the value passed (under the given label, e.g. 'managed hooks', 'previous managed hooks') is null, an array, a primitive, a Map, or a class instance instead of a plain object.
Solutions
- Pass a plain object, e.g. {"PreToolUse":[{...},{...}]} — default {} is not enough; use an object with at least one event (see 319).
- JSON.parse file contents before validating; never pass the raw string.
- If hooks may be absent, pass {} only when the caller tolerates an empty set; otherwise supply real hook events.
- Normalize config loaders to return plain objects (JSON.parse or Object.fromEntries(map)).
Example fix
// before
const hooks = fs.readFileSync(path, 'utf8');
validateManagedHooks(hooks);
// after
const settings = JSON.parse(fs.readFileSync(path, 'utf8'));
validateManagedHooks(settings.hooks ?? {}); Defensive patterns
Strategy: type-guard
Validate before calling
const hooks = settings && typeof settings === 'object' && !Array.isArray(settings) ? settings.hooks : null; if (!isPlainObject(hooks)) throw new Error('settings.hooks must be a plain object'); Type guard
const isHooksConfig = (v) => v !== null && typeof v === 'object' && !Array.isArray(v) && (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);
Try / catch
try { installHooks(rawHooks); } catch (e) { if (e.message.includes('expected a JSON object')) { console.error('Managed hooks config must be a parsed plain object:', e.message); process.exitCode = 1; } else throw e; } Prevention
- JSON.parse file contents before passing them.
- Pass settings.hooks, not the whole settings document.
- Avoid Maps/class instances; use Object.fromEntries if needed.
When it happens
Trigger: Calling managedHooks/materializeManagedHooks/desiredHooks/expectedHooks or the settings APIs that call validateManagedHooks with undefined (settings file missing hooks key), an array of hooks, a JSON string not yet parsed, or a Map.
Common situations: Reading settings.json where the 'hooks' key is absent (undefined); passing the raw file contents string instead of the parsed object; config loaders returning Maps or objects with non-plain prototypes; accidentally passing the whole settings document instead of its hooks section.
Related errors
- Invalid managed hook handler at
- Invalid managed hook handler at
- Invalid managed hook handler at
- application config exceeds local size limit
- Asset request_id/modality does not match the bundle
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/a01640d24da6597c.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/install/claude-settings.js:129
hook.allowedEnvVars !== undefined
&& (!Array.isArray(hook.allowedEnvVars)
|| !hook.allowedEnvVars.every(isNonEmptyString))
) {
throw new Error(`Invalid managed hook handler at ${label}: invalid allowedEnvVars`);
}
return;
}
if (!isNonEmptyString(hook.prompt)) {
throw new Error(`Invalid managed hook handler at ${label}: invalid prompt`);
}
if (hook.model !== undefined && !isNonEmptyString(hook.model)) {
throw new Error(`Invalid managed hook handler at ${label}: invalid model`);
}
}
function validateManagedHooks(managedHooks, label = 'managed hooks') {
if (!isJsonObject(managedHooks)) {
throw new Error(`Invalid ${label}: expected a JSON object`);
}
if (Object.keys(managedHooks).length === 0) {
throw new Error(`Invalid ${label}: expected at least one hook event`);
}
const seenIds = new Set();
for (const [event, entries] of Object.entries(managedHooks)) {
if (!VALID_EVENTS.has(event)) {
throw new Error(`Invalid ${label}: unsupported hook event "${event}"`);
}
if (!Array.isArray(entries)) {
throw new Error(`Invalid ${label}.${event}: expected an array`);
}
if (entries.length === 0) {
throw new Error(`Invalid ${label}.${event}: expected at least one hook entry`);
}
entries.forEach((entry, index) => {View on GitHub (pinned to 8321021c54)