{"record":{"id":"f22a9861784741b2","repo":"affaan-m/ECC","slug":"invalid-label-event-expected-an-array","errorCode":null,"errorMessage":"Invalid ${label}.${event}: expected an array","messagePattern":"Invalid (.+?)\\.(.+?): expected an array","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/claude-settings.js","lineNumber":141,"sourceCode":"    throw new Error(`Invalid managed hook handler at ${label}: invalid model`);\n  }\n}\n\nfunction validateManagedHooks(managedHooks, label = 'managed hooks') {\n  if (!isJsonObject(managedHooks)) {\n    throw new Error(`Invalid ${label}: expected a JSON object`);\n  }\n  if (Object.keys(managedHooks).length === 0) {\n    throw new Error(`Invalid ${label}: expected at least one hook event`);\n  }\n\n  const seenIds = new Set();\n  for (const [event, entries] of Object.entries(managedHooks)) {\n    if (!VALID_EVENTS.has(event)) {\n      throw new Error(`Invalid ${label}: unsupported hook event \"${event}\"`);\n    }\n    if (!Array.isArray(entries)) {\n      throw new Error(`Invalid ${label}.${event}: expected an array`);\n    }\n    if (entries.length === 0) {\n      throw new Error(`Invalid ${label}.${event}: expected at least one hook entry`);\n    }\n\n    entries.forEach((entry, index) => {\n      if (!isJsonObject(entry)) {\n        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)) {","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/claude-settings.js#L123-L159","documentation":"After confirming the event name is valid, validateManagedHooks requires the value under each event key to be an array of hook entries. This error fires when the value is any other type (object, string, number, null, undefined). It protects the later per-entry validation from operating on a non-iterable/mis-shaped value.","triggerScenarios":"Passing a hooks map where an event maps to something other than an array, e.g. { PreToolUse: { id: 'x', ... } } (single entry not wrapped in an array) or { Stop: 'lint.sh' }.","commonSituations":"Hand-merging two hook configs and dropping the array wrapper; JSON with a single object instead of a list under an event; a schema change in an older ECC version whose persisted format used objects per event.","solutions":["Wrap the hook entries in an array under the offending event key.","Re-read the error text: label.event identifies exactly which key has the wrong shape.","Re-run the ECC installer to regenerate the managed-hooks block rather than hand-merging old settings.json content.","Validate the shape with Array.isArray before calling the API."],"exampleFix":"// before\nconst hooks = { PreToolUse: { id: 'fmt', matcher: 'Bash', hooks: [{ type: 'command', command: 'fmt' }] } };\n// after\nconst hooks = { PreToolUse: [{ id: 'fmt', matcher: 'Bash', hooks: [{ type: 'command', command: 'fmt' }] }] };","handlingStrategy":"type-guard","validationCode":"for (const [event, entries] of Object.entries(managedHooks)) {\n  if (!Array.isArray(entries)) throw new Error(`hooks.${event} must be an array`);\n}","typeGuard":"function isHooksMap(v) {\n  return v !== null && typeof v === 'object' && !Array.isArray(v)\n    && Object.values(v).every(Array.isArray);\n}","tryCatchPattern":"try {\n  validateManagedHooks(hooks);\n} catch (err) {\n  if (/expected an array/.test(err.message)) {\n    const event = err.message.match(/\\.(\\w+):/)?.[1];\n    if (event && !Array.isArray(hooks[event])) hooks[event] = [hooks[event]];\n  }\n  throw err;\n}","preventionTips":["Always wrap entries in arrays, even for a single hook","Normalize configs through a builder function that guarantees arrays","Run Array.isArray checks on parsed JSON before passing to the API","Beware JSON round-trips: single objects stay objects unless wrapped"],"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"}