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
- 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.
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
- 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
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
- Refusing to remove Claude hook for event
- Claude settings contains multiple hooks for event
- Invalid : expected hooks. to be an array
- Invalid : expected "hooks" to be a JSON object
- Refusing to disable modified Claude hooks in
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)