affaan-m/ECC · error

Invalid managed hook entry at

Error message

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

What it means

Entries for events that support tool matchers (all events except UserPromptSubmit, Notification, Stop, SubagentStop — see EVENTS_WITHOUT_MATCHER) must include a matcher property, even if it is an empty string. validateManagedHooks throws this when the matcher key is absent on an entry for a matcher-requiring event. Matcher presence is required so the managed block remains deterministic and diffable.

Solutions

  1. Add a matcher to the entry — a string like 'Bash' or '*' (or an object form, see the invalid-matcher error).
  2. Confirm the event actually requires a matcher; if it is UserPromptSubmit/Notification/Stop/SubagentStop, matcher is optional and you can omit it safely.
  3. Check the index in the message (label.event[index]) to find the offending entry.
  4. Use JSON.stringify with explicit matcher field when serializing to avoid dropped keys.

Example fix

// before
{ id: 'lint', hooks: [{ type: 'command', command: 'lint' }] }  // under PreToolUse
// after
{ id: 'lint', matcher: 'Bash', hooks: [{ type: 'command', command: 'lint' }] }
Defensive patterns

Strategy: validation

Validate before calling

const NO_MATCHER = new Set(['UserPromptSubmit', 'Notification', 'Stop', 'SubagentStop']);
for (const [event, entries] of Object.entries(managedHooks)) {
  if (NO_MATCHER.has(event)) continue;
  entries.forEach((e, i) => {
    if (!Object.prototype.hasOwnProperty.call(e, 'matcher')) {
      throw new Error(`${event}[${i}] requires a matcher`);
    }
  });
}

Type guard

function entriesHaveMatchers(hooks) {
  const NO_MATCHER = new Set(['UserPromptSubmit', 'Notification', 'Stop', 'SubagentStop']);
  return Object.entries(hooks).every(([event, entries]) => NO_MATCHER.has(event)
    || entries.every(e => 'matcher' in e));
}

Try / catch

try {
  inspectManagedOperation(hooks);
} catch (err) {
  if (/missing matcher/.test(err.message)) {
    console.error('Add a matcher (string like "Bash" or "*") to this entry, or move it to a matcher-less event');
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing an entry without a matcher key for a matcher-requiring event, e.g. { PreToolUse: [{ id: 'x', hooks: [...] }] }; conversely this does NOT fire for Stop or UserPromptSubmit entries without matcher.

Common situations: Copying an entry shape from a matcher-less event (like Stop) to PreToolUse; writing entries from memory without matcher; destructuring/serializing an entry and dropping undefined matcher fields.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

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

        throw new Error(
          `Invalid managed hook entry at ${label}.${event}[${index}]: expected a JSON object`
        );
      }
      if (typeof entry.id !== 'string' || entry.id.trim() === '') {
        throw new Error(
          `Invalid managed hook entry at ${label}.${event}[${index}]: `
          + 'expected a non-empty unique id'
        );
      }
      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) => {

View on GitHub (pinned to 8321021c54)