affaan-m/ECC · error

Invalid managed hook entry at

Error message

Invalid managed hook entry at ${label}.${event}[${index}]: invalid matcher

What it means

When an entry includes a matcher property, its value must be either a string or a plain JSON object. validateManagedHooks throws this when matcher is present but of another type (number, boolean, array, null). This validates the matcher shape without interpreting its contents, keeping ECC compatible with both string and object matcher forms Claude Code supports.

Solutions

  1. Set matcher to a string pattern (e.g. 'Bash', 'Write|Edit', '*') or a plain object form like { tool: 'Bash' } where supported.
  2. Check the value type with typeof m === 'string' || isPlainObject(m) before calling.
  3. Locate the entry via the index in the message.
  4. When generating from data, coerce the matcher with String(m) if it should be textual.

Example fix

// before
{ id: 'lint', matcher: 123, hooks: [...] }
// after
{ id: 'lint', matcher: 'Bash', hooks: [...] }
Defensive patterns

Strategy: type-guard

Validate before calling

for (const entries of Object.values(managedHooks)) {
  entries.forEach((e, i) => {
    if ('matcher' in e && typeof e.matcher !== 'string'
        && (e.matcher === null || typeof e.matcher !== 'object' || Array.isArray(e.matcher))) {
      throw new Error(`Entry ${i} matcher must be a string or object`);
    }
  });
}

Type guard

function isValidMatcher(m) {
  return typeof m === 'string'
    || (m !== null && typeof m === 'object' && !Array.isArray(m)
        && (Object.getPrototypeOf(m) === Object.prototype || Object.getPrototypeOf(m) === null));
}

Try / catch

try {
  validateManagedHooks(hooks);
} catch (err) {
  if (/invalid matcher/.test(err.message)) {
    console.error('matcher must be a string pattern or a plain object — coerce with String(m) if needed');
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing matcher: 42, matcher: true, matcher: ['Bash'], or matcher: null on an entry; a template/serialization bug emitting a numeric matcher.

Common situations: Programmatic matcher generation that yields non-string values; YAML/JSON editing that turns "Bash" into an unquoted value parsed as boolean/number (e.g. matcher: Yes); a regex literal accidentally passed as a RegExp object instead of a string pattern.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

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

      }
      if (seenIds.has(entry.id)) {
        throw new Error(`Invalid ${label}: expected globally unique id "${entry.id}"`);
      }
      seenIds.add(entry.id);
      if (
        !Object.prototype.hasOwnProperty.call(entry, 'matcher')
        && !EVENTS_WITHOUT_MATCHER.has(event)
      ) {
        throw new Error(
          `Invalid managed hook entry at ${label}.${event}[${index}]: missing matcher`
        );
      }
      if (
        Object.prototype.hasOwnProperty.call(entry, 'matcher')
        && typeof entry.matcher !== 'string'
        && !isJsonObject(entry.matcher)
      ) {
        throw new Error(
          `Invalid managed hook entry at ${label}.${event}[${index}]: invalid matcher`
        );
      }
      if (!Array.isArray(entry.hooks) || entry.hooks.length === 0) {
        throw new Error(
          `Invalid managed hook entry at ${label}.${event}[${index}]: expected hooks`
        );
      }
      entry.hooks.forEach((hook, hookIndex) => {
        validateHookHandler(hook, `${label}.${event}[${index}].hooks[${hookIndex}]`);
      });
    });
  }

  return cloneValue(managedHooks);
}

function validateRecordedManagedHooks(managedHooks, label = 'recorded managed hooks') {

View on GitHub (pinned to 8321021c54)