affaan-m/ECC · error
Invalid managed hook entry at
Error message
Invalid managed hook entry at ${label}.${event}[${index}]: expected a non-empty unique id What it means
Every hook entry must have an id that is a non-empty (non-whitespace) string. validateManagedHooks throws this when id is missing, not a string, an empty string, or whitespace-only. The id is the stable identifier ECC uses to match, diff, and remove managed hooks across installs, so it cannot be absent or blank.
Solutions
- Add a unique non-empty string id to the entry, e.g. id: 'my-hook-lint'.
- Use the index from the message (label.event[index]) to find the entry missing the id.
- When importing existing Claude hooks, assign your own ids to each entry.
- Adopt a naming convention (e.g. 'ecc-<purpose>') so ids stay unique and recognizable.
Example fix
// before
{ matcher: 'Bash', hooks: [{ type: 'command', command: 'lint' }] }
// after
{ id: 'ecc-lint-bash', matcher: 'Bash', hooks: [{ type: 'command', command: 'lint' }] } Defensive patterns
Strategy: validation
Validate before calling
for (const entries of Object.values(managedHooks)) {
entries.forEach((e, i) => {
if (typeof e.id !== 'string' || e.id.trim() === '') {
throw new Error(`Entry ${i} needs a non-empty string id`);
}
});
} Type guard
function allEntriesHaveIds(hooks) {
return Object.values(hooks).flat().every(e =>
e !== null && typeof e === 'object' && typeof e.id === 'string' && e.id.trim() !== '');
} Try / catch
try {
materializeManagedHooks(hooks);
} catch (err) {
if (/non-empty unique id/.test(err.message)) {
const m = err.message.match(/\.(\w+)\[(\d+)\]/);
console.error(`Entry ${m?.[2]} in ${m?.[1]} is missing id — add e.g. id: 'ecc-my-hook'`);
}
throw err;
} Prevention
- Adopt an id naming convention (ecc-<purpose>)
- Assign ids when importing external/Claude-native hooks — they lack id fields
- Never set id to empty string or whitespace
- Validate ids in CI alongside the hook config
When it happens
Trigger: Passing an entry like { matcher: 'Bash', hooks: [...] } without an id field; id: '' or id: ' '; id set to a number or null.
Common situations: Hand-writing a new hook entry and forgetting the id; copying a Claude-Code-native hooks block (which has no id field) into the managed hooks map; trimming/normalization accidentally emptying the id.
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 : expected globally unique id
- invalid canonical health evidence snapshot
- Invalid Claude hooks preference
- Invalid hook entry at
- INVALID_HOOK_MODE
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/8696ce5656b1feed.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/install/claude-settings.js:154
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) => {
if (!isJsonObject(entry)) {
throw new Error(
`Invalid managed hook entry at ${label}.${event}[${index}]: expected a JSON object`
);
}
if (typeof entry.id !== 'string' || entry.id.trim() === '') {
throw new Error(
`Invalid managed hook entry at ${label}.${event}[${index}]: `
+ 'expected a non-empty unique id'
);
}
if (seenIds.has(entry.id)) {
throw new Error(`Invalid ${label}: expected globally unique id "${entry.id}"`);
}
seenIds.add(entry.id);
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')View on GitHub (pinned to 8321021c54)