{"record":{"id":"39d2420733376928","repo":"affaan-m/ECC","slug":"invalid-label-event-expected-at-least-one-hook-entry","errorCode":null,"errorMessage":"Invalid ${label}.${event}: expected at least one hook entry","messagePattern":"Invalid (.+?)\\.(.+?): expected at least one hook entry","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/claude-settings.js","lineNumber":144,"sourceCode":"\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)) {\n        throw new Error(`Invalid ${label}: expected globally unique id \"${entry.id}\"`);\n      }\n      seenIds.add(entry.id);","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/claude-settings.js#L126-L162","documentation":"Each event's entries array must contain at least one hook entry. validateManagedHooks throws this when an event key exists but maps to an empty array, because an empty event group would produce no hooks and indicates a stale or mis-merged configuration.","triggerScenarios":"Passing { PostToolUse: [] } or { SessionEnd: [] } to managedHooks, desiredHooks, expectedHooks, or the other six public callers; deleting all entries under an event during a manual edit but leaving the key.","commonSituations":"Manually removing the last hook under an event in settings.json; a merge conflict resolution that kept the event key but discarded all entries; programmatic filtering that removed every entry for an event.","solutions":["Remove the empty event key entirely if no hooks are needed for it (or pass hooks without that key).","Re-add at least one valid hook entry under the event.","If hooks were removed intentionally, run the uninstall/remove path instead of validating an empty group.","Regenerate the managed hooks block via the install tooling."],"exampleFix":"// before\nconst hooks = { PreToolUse: [] };\n// after\nconst hooks = {};  // or add at least one entry: { PreToolUse: [{ id: 'fmt', matcher: 'Bash', hooks: [...] }] }","handlingStrategy":"validation","validationCode":"for (const [event, entries] of Object.entries(managedHooks)) {\n  if (Array.isArray(entries) && entries.length === 0) delete managedHooks[event];\n}","typeGuard":"function hasNonEmptyEventGroups(v) {\n  return v !== null && typeof v === 'object' && !Array.isArray(v)\n    && Object.values(v).every(e => Array.isArray(e) && e.length > 0);\n}","tryCatchPattern":"try {\n  desiredHooks(hooks);\n} catch (err) {\n  if (/expected at least one hook entry/.test(err.message)) {\n    const event = err.message.match(/\\.(\\w+):/)?.[1];\n    console.warn(`Event \"${event}\" has no entries — drop the key or add a hook`);\n  }\n  throw err;\n}","preventionTips":["Delete event keys entirely when their last hook is removed","Filter empty groups before passing the map: Object.fromEntries(entries.filter(([,v])=>v.length))","In merge tooling, drop keys whose arrays end up empty","Add a pre-validate step that prunes empty arrays"],"tags":["validation","empty-value","hooks"],"backgroundTag":"empty-required-field","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"}