affaan-m/ECC · error
Invalid : expected unique id " " within event
Error message
Invalid ${label}: expected unique id "${entry.id}" within event "${event}" What it means
Within a single hook event, each recorded entry must have a unique id. The validator tracks seen ids per event and throws when a duplicate appears, preventing ambiguous hook identity that would break deduplication and idempotent installs of managed hooks into Claude settings.
Solutions
- Rename one of the duplicate ids so each entry within the event is unique
- Deduplicate entries before validation (keep the newest/first by id)
- Regenerate the recording after fixing the source hooks.json
- If merging recordings programmatically, key entries by id and overwrite instead of concatenating
Example fix
// before
[ { "id": "fmt", ... }, { "id": "fmt", ... } ]
// after
[ { "id": "fmt", ... }, { "id": "fmt-v2", ... } ] Defensive patterns
Strategy: validation
Validate before calling
function hasUniqueIds(entries) { const s = new Set(); return entries.every(e => !s.has(e.id) && s.add(e.id)); } Type guard
null
Try / catch
try { validateRecordedManagedHooks(hooks); } catch (e) { if (e.message.includes('expected unique id')) { hooks = dedupeById(hooks); validateRecordedManagedHooks(hooks); } else throw e; } Prevention
- Derive ids from a naming convention to avoid collisions
- Deduplicate by id when merging hook sources
- Never copy-paste hook entries without renaming the id
- Check for duplicate ids in CI on hooks.json
When it happens
Trigger: Calling validateRecordedManagedHooks (or its callers validate/previousHooks/recordedHooks) where two entries under the same event key share the same id string.
Common situations: Merging two hooks.json sources that both use the default id, copy-pasting an entry block without changing its id, or a plugin upgrade appending its hooks again onto existing settings.
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 . : expected a non-empty hook array
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/cd7230a19fed68c2.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/install/claude-settings.js:208
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);
});
}
return cloneValue(managedHooks);
}
function validateSettings(settings, label = 'Claude settings') {
if (!isJsonObject(settings)) {
throw new Error(`Invalid ${label}: expected a JSON object`);
}
if (Object.prototype.hasOwnProperty.call(settings, 'hooks')) {
if (!isJsonObject(settings.hooks)) {
throw new Error(`Invalid ${label}: expected "hooks" to be a JSON object`);
}View on GitHub (pinned to 8321021c54)