affaan-m/ECC · error
Unknown Claude settings merge mode
Error message
Unknown Claude settings merge mode: ${options.mode} What it means
mergeManagedHooks accepts only mode 'merge', 'repair', or undefined (plus a repair:true flag); any other value throws immediately. This validates the merge strategy before mutating the settings so callers cannot silently trigger unintended behavior with a typo'd mode.
Solutions
- Change options.mode to one of 'merge' or 'repair' (or omit it entirely).
- Check the call site for typos or case mismatches in the mode string.
- If the mode comes from user input, validate/normalize it against the allowed set before calling.
- Prefer passing repair: true over mode: 'repair' if that is how repair semantics are triggered in your code.
Example fix
// before
await mergeManagedHooks({ settings, mode: 'replace' });
// after
await mergeManagedHooks({ settings, mode: 'merge' }); Defensive patterns
Strategy: validation
Validate before calling
const MODES = ['merge', 'repair', undefined]; if (!MODES.includes(options.mode)) throw new Error(`mode must be 'merge', 'repair', or omitted`);
Type guard
function isMergeMode(v) { return v === undefined || v === 'merge' || v === 'repair'; } Try / catch
try {
await mergeManagedHooks(opts);
} catch (error) {
if (error.message.startsWith('Unknown Claude settings merge mode')) {
console.error(`Bad mode '${opts.mode}'; use 'merge' or 'repair'`);
return;
}
throw error;
} Prevention
- Validate CLI/config values against an allowlist before passing options.mode
- Use a constant (MODE_MERGE/MODE_REPAIR) instead of raw strings at call sites
- Document the two valid modes wherever the API is wrapped
- Trim/normalize user-supplied mode strings before use
When it happens
Trigger: Calling mergeManagedHooks({ mode: 'force' }) or any misspelled/unsupported mode string ('Merge', 'replace', 'repair ' with trailing space); passing mode from unvalidated CLI/config input.
Common situations: A typo in a script or wrapper invoking the installer API, passing a CLI flag value straight through without validation, or copying an example that used an older API surface with different mode names.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- artifact has invalid modality binding
- assigneeKind must be 'agent' or 'human'.
- capsule.bad_lineage
- Claude settings contains multiple hooks for event
- CV effect has an invalid subject anchor
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/910f49b5f57a8ff8.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/install/claude-settings.js:486
function managedEntryFor(managedHooks, event, id) {
const entries = managedHooks && managedHooks[event];
if (!Array.isArray(entries)) {
return null;
}
return entries.find(entry => entry.id === id) || null;
}
function mergeManagedHooks(settings, managedHooks, options = {}) {
const validatedSettings = validateSettings(settings);
const desiredHooks = validateManagedHooks(managedHooks);
const previousHooks = options.previousManagedHooks === undefined
|| options.previousManagedHooks === null
? null
: validateRecordedManagedHooks(options.previousManagedHooks, 'previous managed hooks');
const repair = options.mode === 'repair' || options.repair === true;
if (options.mode !== undefined && options.mode !== 'merge' && options.mode !== 'repair') {
throw new Error(`Unknown Claude settings merge mode: ${options.mode}`);
}
let nextHooks = validatedSettings.hooks
? cloneValue(validatedSettings.hooks)
: {};
const added = [];
const updated = [];
const unchanged = [];
const removed = [];
if (previousHooks) {
for (const [event, previousEntries] of Object.entries(previousHooks)) {
let eventEntries = nextHooks[event] ? cloneValue(nextHooks[event]) : [];
for (const previousEntry of previousEntries) {
if (managedEntryFor(desiredHooks, event, previousEntry.id)) continue;
const match = assertUnambiguousMatch(eventEntries, event, previousEntry.id);
if (!match) continue;
if (!isDeepStrictEqual(match.entry, previousEntry)) {View on GitHub (pinned to 8321021c54)