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

  1. Change options.mode to one of 'merge' or 'repair' (or omit it entirely).
  2. Check the call site for typos or case mismatches in the mode string.
  3. If the mode comes from user input, validate/normalize it against the allowed set before calling.
  4. 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

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


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)