{"record":{"id":"6569b0829b6690b1","repo":"affaan-m/ECC","slug":"invalid-managed-hook-entry-at-label-event-index-invalid","errorCode":null,"errorMessage":"Invalid managed hook entry at ${label}.${event}[${index}]: invalid matcher","messagePattern":"Invalid managed hook entry at (.+?)\\.(.+?)\\[(.+?)\\]: invalid matcher","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/claude-settings.js","lineNumber":176,"sourceCode":"      }\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) => {\n        validateHookHandler(hook, `${label}.${event}[${index}].hooks[${hookIndex}]`);\n      });\n    });\n  }\n\n  return cloneValue(managedHooks);\n}\n\nfunction validateRecordedManagedHooks(managedHooks, label = 'recorded managed hooks') {","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/claude-settings.js#L158-L194","documentation":"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.","triggerScenarios":"Passing matcher: 42, matcher: true, matcher: ['Bash'], or matcher: null on an entry; a template/serialization bug emitting a numeric matcher.","commonSituations":"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.","solutions":["Set matcher to a string pattern (e.g. 'Bash', 'Write|Edit', '*') or a plain object form like { tool: 'Bash' } where supported.","Check the value type with typeof m === 'string' || isPlainObject(m) before calling.","Locate the entry via the index in the message.","When generating from data, coerce the matcher with String(m) if it should be textual."],"exampleFix":"// before\n{ id: 'lint', matcher: 123, hooks: [...] }\n// after\n{ id: 'lint', matcher: 'Bash', hooks: [...] }","handlingStrategy":"type-guard","validationCode":"for (const entries of Object.values(managedHooks)) {\n  entries.forEach((e, i) => {\n    if ('matcher' in e && typeof e.matcher !== 'string'\n        && (e.matcher === null || typeof e.matcher !== 'object' || Array.isArray(e.matcher))) {\n      throw new Error(`Entry ${i} matcher must be a string or object`);\n    }\n  });\n}","typeGuard":"function isValidMatcher(m) {\n  return typeof m === 'string'\n    || (m !== null && typeof m === 'object' && !Array.isArray(m)\n        && (Object.getPrototypeOf(m) === Object.prototype || Object.getPrototypeOf(m) === null));\n}","tryCatchPattern":"try {\n  validateManagedHooks(hooks);\n} catch (err) {\n  if (/invalid matcher/.test(err.message)) {\n    console.error('matcher must be a string pattern or a plain object — coerce with String(m) if needed');\n  }\n  throw err;\n}","preventionTips":["Quote matcher values in YAML/JSON so they are strings, not booleans/numbers (e.g. matcher: \"Yes\" pitfall)","Convert RegExp objects to .source or .toString() before use","Validate matcher type in your config loader","Avoid passing null — omit the key for matcher-less events instead"],"tags":["validation","type-mismatch","hooks"],"backgroundTag":"type-mismatch","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"}