{"record":{"id":"15df45680c90d86b","repo":"affaan-m/ECC","slug":"refusing-to-overwrite-claude-hook-for-event-event-and-id","errorCode":null,"errorMessage":"Refusing to overwrite Claude hook for event \"${event}\" and id \"${desiredEntry.id}\"${driftReason}","messagePattern":"Refusing to overwrite Claude hook for event \"(.+?)\" and id \"(.+?)\"(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/claude-settings.js","lineNumber":536,"sourceCode":"\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) {\n        eventEntries = [...eventEntries, cloneValue(desiredEntry)];\n        added.push(reference(event, desiredEntry.id));\n        continue;\n      }\n      if (isDeepStrictEqual(match.entry, desiredEntry)) {\n        unchanged.push(reference(event, desiredEntry.id));\n        continue;\n      }\n\n      const previousEntry = managedEntryFor(previousHooks, event, desiredEntry.id);\n      if (!repair && (!previousEntry || !isDeepStrictEqual(match.entry, previousEntry))) {\n        const driftReason = previousEntry ? ' because the previous managed entry has drifted' : '';\n        throw new Error(\n          `Refusing to overwrite Claude hook for event \"${event}\" and id `\n          + `\"${desiredEntry.id}\"${driftReason}`\n        );\n      }\n\n      eventEntries = eventEntries.map((entry, index) => (\n        index === match.index ? cloneValue(desiredEntry) : entry\n      ));\n      updated.push(reference(event, desiredEntry.id));\n    }\n    if (desiredEntries.length > 0) {\n      nextHooks = { ...nextHooks, [event]: eventEntries };\n    }\n  }\n\n  const nextSettings = Object.keys(nextHooks).length > 0\n    ? { ...validatedSettings, hooks: nextHooks }\n    : validatedSettings;","sourceCodeStart":518,"sourceCodeEnd":554,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/claude-settings.js#L518-L554","documentation":"During mergeManagedHooks, when a desired hook already exists in settings (matched by id), the merge refuses to overwrite it unless the current entry is deep-equal to the recorded previous managed entry (or repair mode is on). The message's driftReason distinguishes pure overwrites from ones where the recorded entry itself has drifted. This prevents clobbering user-modified hooks.","triggerScenarios":"Reinstalling/updating ECC hooks when the current settings entry with the same hook id differs from the recorded previous entry, or when no previous entry was recorded at all (previousEntry undefined) and repair is not enabled.","commonSituations":"The user tweaked a managed hook between installs; settings were restored from a different machine so recorded entries no longer match; upgrading ECC to a new hook definition while old recorded metadata is missing.","solutions":["Re-run with mode: 'repair' (or repair: true) if you accept the new hook definition overwriting local changes.","Revert manual edits to the hook entry so it matches the previously installed version, then rerun the merge.","If no previous entry was recorded (fresh environment), run in repair mode once to establish the baseline.","Back up settings.json before repair mode so customizations can be reapplied afterward."],"exampleFix":"// before\nawait mergeManagedHooks({ settings, desiredHooks, previousManagedHooks });\n// after\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.startsWith('Refusing to overwrite Claude hook')) {\n    console.error('Hook drifted or unrecorded; rerun with mode: repair to accept new definition');\n    return;\n  }\n  throw error;\n}","preventionTips":["Keep previousManagedHooks metadata in sync when copying settings between machines","Treat managed hook entries as installer-owned; customize via separate hooks","Run repair mode intentionally (with backup) when upgrading hook definitions","Review settings.json diffs between installs to spot drift early"],"tags":["settings","hooks","drift","overwrite-protection"],"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"}