affaan-m/ECC · error
Invalid hooks config: expected a JSON object with a hooks…
Error message
Invalid hooks config: expected a JSON object with a hooks object
What it means
materializeManagedHooks turns a hooks config (as found in hooks.json) into concrete managed hooks for a target root. It requires the config to be a JSON object containing a hooks object; anything else cannot be materialized, so this error is thrown up front. This catches wrong files or wrong config shapes before path substitution and validation run.
Solutions
- Pass the parsed hooks.json object which has a top-level hooks object
- If you have raw text, JSON.parse it first and confirm a hooks key exists
- Verify the file path points to hooks.json, not settings.json
- Add a pre-check: config && typeof config.hooks === 'object'
Example fix
// before
materializeManagedHooks(JSON.parse(await fs.readFile('hooks.json', 'utf8')).hooks, root)
// after
const cfg = JSON.parse(await fs.readFile('hooks.json', 'utf8'));
materializeManagedHooks(cfg, root) // pass the wrapper, not cfg.hooks Defensive patterns
Strategy: validation
Validate before calling
const isHooksConfig = (c) => c && typeof c === 'object' && !Array.isArray(c) && c.hooks && typeof c.hooks === 'object' && !Array.isArray(c.hooks);
Type guard
const hasHooksObject = (v) => typeof v === 'object' && v !== null && isJsonObject(v.hooks);
Try / catch
try { return materializeManagedHooks(cfg, root); } catch (e) { if (e.message.includes('expected a JSON object with a hooks object')) { console.error('Passed value is not a hooks.json wrapper; check the file path'); } throw e; } Prevention
- Pass the whole parsed hooks.json object, not its .hooks field
- Confirm the loaded file is hooks.json (top-level hooks key)
- JSON.parse raw text before calling the library
- Schema-check hooks.json in CI
When it happens
Trigger: Calling materializeManagedHooks with hooksConfig that is not an object, or whose .hooks property is missing or not an object — e.g. passing the parsed settings.json instead of hooks.json, or a hooks.json without a top-level "hooks" key.
Common situations: Pointing the installer at the wrong JSON file, a plugin's hooks.json missing the hooks wrapper after a bad edit, or passing a string of raw JSON instead of a parsed object.
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 hook entry at
- Invalid . : expected a non-empty hook array
- Invalid : expected at least one hook event
- Invalid managed hook handler at
- Invalid managed hook handler at
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/a6612fbdd8a9c415.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/install/claude-settings.js:299
...entry,
hooks: entry.hooks.map(hook => ({
...hook,
...(typeof hook.command === 'string' || Array.isArray(hook.command)
? {
command: Array.isArray(hook.command)
? hook.command.map(resolveCommand)
: resolveCommand(hook.command),
}
: {}),
})),
})),
])
);
}
function materializeManagedHooks(hooksConfig, targetRoot) {
if (!isJsonObject(hooksConfig) || !isJsonObject(hooksConfig.hooks)) {
throw new Error('Invalid hooks config: expected a JSON object with a hooks object');
}
if (!isNonEmptyString(targetRoot)) {
throw new Error('Invalid Claude target root: expected a non-empty string');
}
return validateManagedHooks(resolveManagedHookCommands(
replacePluginRootPlaceholders(hooksConfig.hooks, targetRoot),
targetRoot
));
}
function parseSettings(rawSettings, label = 'Claude settings') {
let settings;
try {
settings = JSON.parse(rawSettings);
} catch (error) {
throw new Error(`Failed to parse ${label}: ${error.message}`, { cause: error });
}
return validateSettings(settings, label);View on GitHub (pinned to 8321021c54)