affaan-m/ECC · error
Invalid managed hook entry at
Error message
Invalid managed hook entry at ${label}.${event}[${index}]: expected hooks What it means
Every hook entry must have a hooks array containing at least one handler definition. validateManagedHooks throws this when entry.hooks is missing, not an array, or an empty array. The hooks array holds the actual handler objects (command/http/prompt/agent) that Claude Code executes.
Solutions
- Ensure each entry has hooks: [ ... ] with at least one handler object, e.g. { type: 'command', command: 'npx lint' }.
- Wrap a single handler object in an array.
- Locate the offending entry via the index in the message.
- If the handler was intentionally removed, delete the whole entry instead of leaving an empty hooks array.
Example fix
// before
{ id: 'lint', matcher: 'Bash', hooks: [] }
// after
{ id: 'lint', matcher: 'Bash', hooks: [{ type: 'command', command: 'npm run lint' }] } Defensive patterns
Strategy: validation
Validate before calling
for (const entries of Object.values(managedHooks)) {
entries.forEach((e, i) => {
if (!Array.isArray(e.hooks) || e.hooks.length === 0) {
throw new Error(`Entry ${i} (${e.id}) must have a non-empty hooks array`);
}
});
} Type guard
function allEntriesHaveHandlers(hooks) {
return Object.values(hooks).flat().every(e =>
e !== null && typeof e === 'object' && Array.isArray(e.hooks) && e.hooks.length > 0);
} Try / catch
try {
materializeManagedHooks(hooks);
} catch (err) {
if (/expected hooks/.test(err.message)) {
console.error('Each entry needs hooks: [{ type: "command", command: "..." }] with at least one handler');
}
throw err;
} Prevention
- Use an entry factory that requires a handler argument
- Wrap single handlers in arrays: hooks: [handler]
- Delete entries instead of emptying their hooks array
- Validate handler arrays in CI for hook configs
When it happens
Trigger: Passing { id: 'x', matcher: 'Bash' } with no hooks field; hooks: []; hooks: 'cmd' (a string instead of an array).
Common situations: Hand-writing an entry and forgetting the hooks wrapper; removing all handlers from an entry during cleanup; a template that emits a singular hook object instead of an array; copying Claude-native settings where a flat handler replaced the array.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- Invalid managed hook entry at
- Canonical session snapshot requires session.sourceTarget to…
- Canonical session snapshot requires session to be an object
- Canonical session snapshot requires workers to be an array
- clip has no 'path
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/84c213292c6e8269.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/install/claude-settings.js:181
if (
!Object.prototype.hasOwnProperty.call(entry, 'matcher')
&& !EVENTS_WITHOUT_MATCHER.has(event)
) {
throw new Error(
`Invalid managed hook entry at ${label}.${event}[${index}]: missing matcher`
);
}
if (
Object.prototype.hasOwnProperty.call(entry, 'matcher')
&& typeof entry.matcher !== 'string'
&& !isJsonObject(entry.matcher)
) {
throw new Error(
`Invalid managed hook entry at ${label}.${event}[${index}]: invalid matcher`
);
}
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) {View on GitHub (pinned to 8321021c54)