{"record":{"id":"0ea627e1c111d566","repo":"affaan-m/ECC","slug":"refusing-to-remove-claude-hook-for-event-event-and-id","errorCode":null,"errorMessage":"Refusing to remove Claude hook for event \"${event}\" and id \"${previousEntry.id}\" because the previous managed entry has drifted","messagePattern":"Refusing to remove Claude hook for event \"(.+?)\" and id \"(.+?)\" because the previous managed entry has drifted","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/claude-settings.js","lineNumber":505,"sourceCode":"  }\n\n  let nextHooks = validatedSettings.hooks\n    ? cloneValue(validatedSettings.hooks)\n    : {};\n  const added = [];\n  const updated = [];\n  const unchanged = [];\n  const removed = [];\n\n  if (previousHooks) {\n    for (const [event, previousEntries] of Object.entries(previousHooks)) {\n      let eventEntries = nextHooks[event] ? cloneValue(nextHooks[event]) : [];\n      for (const previousEntry of previousEntries) {\n        if (managedEntryFor(desiredHooks, event, previousEntry.id)) continue;\n        const match = assertUnambiguousMatch(eventEntries, event, previousEntry.id);\n        if (!match) continue;\n        if (!isDeepStrictEqual(match.entry, previousEntry)) {\n          throw new Error(\n            `Refusing to remove Claude hook for event \"${event}\" and id `\n            + `\"${previousEntry.id}\" because the previous managed entry has drifted`\n          );\n        }\n        eventEntries = eventEntries.filter((_entry, index) => index !== match.index);\n        removed.push(reference(event, previousEntry.id));\n      }\n      nextHooks = eventEntries.length > 0\n        ? { ...nextHooks, [event]: eventEntries }\n        : withoutProperty(nextHooks, event);\n    }\n  }\n\n  for (const [event, desiredEntries] of Object.entries(desiredHooks)) {\n    let eventEntries = nextHooks[event] ? cloneValue(nextHooks[event]) : [];\n    for (const desiredEntry of desiredEntries) {\n      const match = assertUnambiguousMatch(eventEntries, event, desiredEntry.id);\n      if (!match) {","sourceCodeStart":487,"sourceCodeEnd":523,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/claude-settings.js#L487-L523","documentation":"During mergeManagedHooks removal of stale managed hooks, if a previously recorded managed hook entry exists in settings but its contents no longer deep-equal the recorded previous entry, the merge refuses to remove it. This protects hand-modified or externally changed hooks from being silently deleted. Repair mode and exact matches proceed with removal.","triggerScenarios":"Running an uninstall/merge where previousManagedHooks records a hook that the user (or another tool) has since edited in settings.json; the recorded id still matches but the entry object has drifted.","commonSituations":"A user customized command strings, matchers, or timeouts of an ECC-managed hook in settings.json, then ran the installer which wanted to remove or replace the stale hook.","solutions":["Revert your manual edits to that hook entry so it matches the previously installed version, then rerun.","Run the operation in repair mode (mode: 'repair' or repair: true) if you intentionally want the drifted hook replaced/removed.","Manually delete the drifted hook from settings.json if you no longer want it, then rerun the merge.","Back up settings.json before using repair mode so local customizations can be recovered."],"exampleFix":"// before\nawait mergeManagedHooks({ settings, desiredHooks, previousManagedHooks });\n// after (explicitly allow replacing drifted entries)\nawait mergeManagedHooks({ settings, desiredHooks, previousManagedHooks, mode: 'repair' });","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await mergeManagedHooks({ settings, desiredHooks, previousManagedHooks });\n} catch (error) {\n  if (error.message.includes('previous managed entry has drifted')) {\n    // ask user or run repair deliberately\n    await mergeManagedHooks({ settings, desiredHooks, previousManagedHooks, mode: 'repair' });\n  } else throw error;\n}","preventionTips":["Don't hand-edit ECC-managed hook entries; keep custom hooks under distinct ids","Back up settings.json before reinstall/update operations","Sync the recorded previousManagedHooks state together with settings.json","Use repair mode explicitly when you intend to replace local modifications"],"tags":["settings","hooks","drift","safety-guard"],"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"}