{"record":{"id":"5481724aaf9aacbb","repo":"affaan-m/ECC","slug":"invalid-managed-hook-entry-at-label-event-index-missing","errorCode":null,"errorMessage":"Invalid managed hook entry at ${label}.${event}[${index}]: missing matcher","messagePattern":"Invalid managed hook entry at (.+?)\\.(.+?)\\[(.+?)\\]: missing matcher","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/claude-settings.js","lineNumber":167,"sourceCode":"        throw new Error(\n          `Invalid managed hook entry at ${label}.${event}[${index}]: expected a JSON object`\n        );\n      }\n      if (typeof entry.id !== 'string' || entry.id.trim() === '') {\n        throw new Error(\n          `Invalid managed hook entry at ${label}.${event}[${index}]: `\n          + 'expected a non-empty unique id'\n        );\n      }\n      if (seenIds.has(entry.id)) {\n        throw new Error(`Invalid ${label}: expected globally unique id \"${entry.id}\"`);\n      }\n      seenIds.add(entry.id);\n      if (\n        !Object.prototype.hasOwnProperty.call(entry, 'matcher')\n        && !EVENTS_WITHOUT_MATCHER.has(event)\n      ) {\n        throw new Error(\n          `Invalid managed hook entry at ${label}.${event}[${index}]: missing matcher`\n        );\n      }\n      if (\n        Object.prototype.hasOwnProperty.call(entry, 'matcher')\n        && typeof entry.matcher !== 'string'\n        && !isJsonObject(entry.matcher)\n      ) {\n        throw new Error(\n          `Invalid managed hook entry at ${label}.${event}[${index}]: invalid matcher`\n        );\n      }\n      if (!Array.isArray(entry.hooks) || entry.hooks.length === 0) {\n        throw new Error(\n          `Invalid managed hook entry at ${label}.${event}[${index}]: expected hooks`\n        );\n      }\n      entry.hooks.forEach((hook, hookIndex) => {","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/claude-settings.js#L149-L185","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Add a matcher to the entry — a string like 'Bash' or '*' (or an object form, see the invalid-matcher error).","Confirm the event actually requires a matcher; if it is UserPromptSubmit/Notification/Stop/SubagentStop, matcher is optional and you can omit it safely.","Check the index in the message (label.event[index]) to find the offending entry.","Use JSON.stringify with explicit matcher field when serializing to avoid dropped keys."],"exampleFix":"// before\n{ id: 'lint', hooks: [{ type: 'command', command: 'lint' }] }  // under PreToolUse\n// after\n{ id: 'lint', matcher: 'Bash', hooks: [{ type: 'command', command: 'lint' }] }","handlingStrategy":"validation","validationCode":"const NO_MATCHER = new Set(['UserPromptSubmit', 'Notification', 'Stop', 'SubagentStop']);\nfor (const [event, entries] of Object.entries(managedHooks)) {\n  if (NO_MATCHER.has(event)) continue;\n  entries.forEach((e, i) => {\n    if (!Object.prototype.hasOwnProperty.call(e, 'matcher')) {\n      throw new Error(`${event}[${i}] requires a matcher`);\n    }\n  });\n}","typeGuard":"function entriesHaveMatchers(hooks) {\n  const NO_MATCHER = new Set(['UserPromptSubmit', 'Notification', 'Stop', 'SubagentStop']);\n  return Object.entries(hooks).every(([event, entries]) => NO_MATCHER.has(event)\n    || entries.every(e => 'matcher' in e));\n}","tryCatchPattern":"try {\n  inspectManagedOperation(hooks);\n} catch (err) {\n  if (/missing matcher/.test(err.message)) {\n    console.error('Add a matcher (string like \"Bash\" or \"*\") to this entry, or move it to a matcher-less event');\n  }\n  throw err;\n}","preventionTips":["Remember matcher is optional ONLY for UserPromptSubmit/Notification/Stop/SubagentStop","Use '*' when you want to match all tools","Default matcher: '*' in your entry factory so the field is always present","Copy entry shapes from the same event family, not across events"],"tags":["validation","missing-field","hooks"],"backgroundTag":"missing-required-argument","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}