{"record":{"id":"bd8c2e6256bd09a0","repo":"affaan-m/ECC","slug":"claude-settings-contains-multiple-hooks-for-event-event-and","errorCode":null,"errorMessage":"Claude settings contains multiple hooks for event \"${event}\" and id \"${id}\"","messagePattern":"Claude settings contains multiple hooks for event \"(.+?)\" and id \"(.+?)\"","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/claude-settings.js","lineNumber":462,"sourceCode":"    return update();\n  }\n  return runWithSettingsLock(settingsPath, update);\n}\n\nfunction reference(event, id) {\n  return { event, id };\n}\n\nfunction entriesMatchingId(entries, id) {\n  return entries\n    .map((entry, index) => ({ entry, index }))\n    .filter(candidate => isJsonObject(candidate.entry) && candidate.entry.id === id);\n}\n\nfunction assertUnambiguousMatch(entries, event, id) {\n  const matches = entriesMatchingId(entries, id);\n  if (matches.length > 1) {\n    throw new Error(\n      `Claude settings contains multiple hooks for event \"${event}\" and id \"${id}\"`\n    );\n  }\n  return matches[0] || null;\n}\n\nfunction managedEntryFor(managedHooks, event, id) {\n  const entries = managedHooks && managedHooks[event];\n  if (!Array.isArray(entries)) {\n    return null;\n  }\n  return entries.find(entry => entry.id === id) || null;\n}\n\nfunction mergeManagedHooks(settings, managedHooks, options = {}) {\n  const validatedSettings = validateSettings(settings);\n  const desiredHooks = validateManagedHooks(managedHooks);\n  const previousHooks = options.previousManagedHooks === undefined","sourceCodeStart":444,"sourceCodeEnd":480,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/claude-settings.js#L444-L480","documentation":"assertUnambiguousMatch finds hook entries matching a given event and managed id, and throws if more than one entry in the Claude settings contains that id, because removal or replacement would be ambiguous. Exactly zero or one entry is required; the single match (or null) is returned.","triggerScenarios":"Calling uninstallManagedHooks or match for event/id when settings.json manually contains the same hook id twice under the same event array (e.g. duplicated by a hand-edit or a copy-paste of a hook block).","commonSituations":"A user duplicated a managed hook entry while editing settings.json, restored an old file over a newer one producing duplicates, or merged settings from two machines concatenating hook arrays.","solutions":["Open settings.json and remove the duplicate entry so the id appears at most once under the event.","Keep only the entry whose contents match the current ECC-managed hook definition.","If unsure which duplicate is correct, back up settings.json and let the installer re-add the managed hook cleanly.","Rerun the install/repair after de-duplicating."],"exampleFix":"// before (settings.json)\n\"PostToolUse\": [ {\"id\": \"fmt\"}, {\"id\": \"fmt\"} ]\n// after\n\"PostToolUse\": [ {\"id\": \"fmt\"} ]","handlingStrategy":"validation","validationCode":"function hasDuplicateHookIds(settings, event, id) {\n  const entries = (settings.hooks?.[event] ?? []).filter(e => e && e.id === id);\n  return entries.length > 1;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await uninstallManagedHooks(path, event, id);\n} catch (error) {\n  if (error.message.includes('multiple hooks for event')) {\n    console.error('De-duplicate the hook id in settings.json, then retry');\n    return;\n  }\n  throw error;\n}","preventionTips":["Edit hooks in settings.json one entry at a time and validate JSON after each change","Don't concatenate hook arrays when merging settings from machines","Let the installer manage hook entries instead of copying them manually","Diff settings.json against a known-good version before running install/uninstall"],"tags":["settings","hooks","duplicate","config"],"backgroundTag":"invalid-state-transition","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}