{"record":{"id":"44e5e5b454e13484","repo":"affaan-m/ECC","slug":"refusing-to-disable-modified-claude-hooks-in-operation","errorCode":null,"errorMessage":"Refusing to disable modified Claude hooks in ${operation.destinationPath}; run the ECC uninstaller to review retained entries.","messagePattern":"Refusing to disable modified Claude hooks in (.+?); run the ECC uninstaller to review retained entries\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/apply.js","lineNumber":333,"sourceCode":"function preflightClaudeSettingsOperations(plan) {\n  const settingsOperations = plan.operations.filter(operation => (\n    operation.kind === 'update-claude-settings'\n    || operation.kind === 'remove-claude-settings-hooks'\n  ));\n  if (settingsOperations.length === 0) {\n    return new Map();\n  }\n\n  const previousState = readPreviousInstallState(plan);\n  return new Map(settingsOperations.map(operation => {\n    assertSafeInstallOperation(plan, operation);\n    const managedHooks = validateManagedHooks(operation.managedHooks);\n    const settings = readSettings(operation.destinationPath);\n    const previousManagedHooks = findPreviousManagedHooks(previousState, plan, operation);\n    if (operation.kind === 'remove-claude-settings-hooks') {\n      const removal = uninstallManagedHooks(settings, managedHooks);\n      if (removal.retained.length > 0) {\n        throw new Error(\n          `Refusing to disable modified Claude hooks in ${operation.destinationPath}; `\n          + 'run the ECC uninstaller to review retained entries.'\n        );\n      }\n    } else {\n      mergeManagedHooks(settings, managedHooks, { previousManagedHooks });\n    }\n    return [operation, { managedHooks, previousManagedHooks }];\n  }));\n}\n\nfunction prepareHookConsentMigration(plan, migration) {\n  if (plan.hookConsent !== 'declined') {\n    return migration;\n  }\n  const previousState = readPreviousInstallState(plan);\n  if (!previousState) {\n    return migration;","sourceCodeStart":315,"sourceCodeEnd":351,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/apply.js#L315-L351","documentation":"During a preflight of Claude settings operations, ECC found managed hooks in the destination settings file that no longer match what ECC originally wrote. Rather than silently disabling user-modified hooks, the installer aborts and directs the user to the ECC uninstaller, which offers an interactive review of each retained entry. This is a safety gate against destroying manual customizations.","triggerScenarios":"Calling preflightClaudeSettingsOperations (via previewInstallPlan or preparedClaudeSettings) with a 'remove-claude-settings-hooks' operation where uninstallManagedHooks(settings, managedHooks) returns a non-empty retained list — i.e. the hook entries on disk differ from the previously recorded managed hook state.","commonSituations":"A developer hand-edited ~/.claude/settings.json (changed the hook command, timeout, or matcher) after installing ECC, then re-runs the installer or an uninstall flow that needs to disable those hooks; or the install-state file is stale/missing so ECC cannot match the hooks it wrote.","solutions":["Run the ECC uninstaller and review the retained hook entries, choosing which to keep or discard.","Manually revert your edits to the managed hooks in the settings file so they match the original ECC output, then re-run the install.","Remove the modified hook entries from the settings file yourself, then re-run the installer.","Delete/repair the install-state record if it is stale, so previousManagedHooks match disk again."],"exampleFix":"// before (hand-edited settings.json hook command)\n\"command\": \"node ~/.claude/scripts/hooks/my-custom-hook.js\"\n// after (restore ECC-managed hook or remove it, then rerun installer)\n\"command\": \"node ~/.claude/scripts/hooks/suggest-compact.js\"","handlingStrategy":"validation","validationCode":"import { findPreviousManagedHooks, readSettings } from './scripts/lib/install/apply.js';\nconst settings = readSettings(settingsPath);\nconst prev = findPreviousManagedHooks(state, plan, op);\nif (prev.length !== settingsMatchingHooks(settings).length) {\n  console.warn('Managed hooks were modified; run the ECC uninstaller first.');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await previewInstallPlan(plan);\n} catch (e) {\n  if (e.message.includes('Refusing to disable modified Claude hooks')) {\n    await runUninstallerInteractive(); // review retained entries\n  } else throw e;\n}","preventionTips":["Never hand-edit hook entries ECC manages in ~/.claude/settings.json.","Run install in preview mode before applying to catch modified-hook conflicts early.","Keep install-state files intact so previousManagedHooks can be matched against disk."],"tags":["hooks","installer","safety-gate","settings"],"backgroundTag":"conflicting-config-options","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"}