affaan-m/ECC · error

Refusing to overwrite Claude hook for event

Error message

Refusing to overwrite Claude hook for event "${event}" and id "${desiredEntry.id}"${driftReason}

What it means

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.

Solutions

  1. Re-run with mode: 'repair' (or repair: true) if you accept the new hook definition overwriting local changes.
  2. Revert manual edits to the hook entry so it matches the previously installed version, then rerun the merge.
  3. If no previous entry was recorded (fresh environment), run in repair mode once to establish the baseline.
  4. Back up settings.json before repair mode so customizations can be reapplied afterward.

Example fix

// before
await mergeManagedHooks({ settings, desiredHooks, previousManagedHooks });
// after
await mergeManagedHooks({ settings, desiredHooks, previousManagedHooks, mode: 'repair' });
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await mergeManagedHooks({ settings, desiredHooks, previousManagedHooks });
} catch (error) {
  if (error.message.startsWith('Refusing to overwrite Claude hook')) {
    console.error('Hook drifted or unrecorded; rerun with mode: repair to accept new definition');
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/15df45680c90d86b. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/install/claude-settings.js:536

  for (const [event, desiredEntries] of Object.entries(desiredHooks)) {
    let eventEntries = nextHooks[event] ? cloneValue(nextHooks[event]) : [];
    for (const desiredEntry of desiredEntries) {
      const match = assertUnambiguousMatch(eventEntries, event, desiredEntry.id);
      if (!match) {
        eventEntries = [...eventEntries, cloneValue(desiredEntry)];
        added.push(reference(event, desiredEntry.id));
        continue;
      }
      if (isDeepStrictEqual(match.entry, desiredEntry)) {
        unchanged.push(reference(event, desiredEntry.id));
        continue;
      }

      const previousEntry = managedEntryFor(previousHooks, event, desiredEntry.id);
      if (!repair && (!previousEntry || !isDeepStrictEqual(match.entry, previousEntry))) {
        const driftReason = previousEntry ? ' because the previous managed entry has drifted' : '';
        throw new Error(
          `Refusing to overwrite Claude hook for event "${event}" and id `
          + `"${desiredEntry.id}"${driftReason}`
        );
      }

      eventEntries = eventEntries.map((entry, index) => (
        index === match.index ? cloneValue(desiredEntry) : entry
      ));
      updated.push(reference(event, desiredEntry.id));
    }
    if (desiredEntries.length > 0) {
      nextHooks = { ...nextHooks, [event]: eventEntries };
    }
  }

  const nextSettings = Object.keys(nextHooks).length > 0
    ? { ...validatedSettings, hooks: nextHooks }
    : validatedSettings;

View on GitHub (pinned to 8321021c54)