{"record":{"id":"1420200d0f4b84c3","repo":"affaan-m/ECC","slug":"invalid-label-expected-hooks-to-be-a-json-object","errorCode":null,"errorMessage":"Invalid ${label}: expected \"hooks\" to be a JSON object","messagePattern":"Invalid (.+?): expected \"hooks\" to be a JSON object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/claude-settings.js","lineNumber":225,"sourceCode":"      if (seenIds.has(entry.id)) {\n        throw new Error(\n          `Invalid ${label}: expected unique id \"${entry.id}\" within event \"${event}\"`\n        );\n      }\n      seenIds.add(entry.id);\n    });\n  }\n  return cloneValue(managedHooks);\n}\n\nfunction validateSettings(settings, label = 'Claude settings') {\n  if (!isJsonObject(settings)) {\n    throw new Error(`Invalid ${label}: expected a JSON object`);\n  }\n\n  if (Object.prototype.hasOwnProperty.call(settings, 'hooks')) {\n    if (!isJsonObject(settings.hooks)) {\n      throw new Error(`Invalid ${label}: expected \"hooks\" to be a JSON object`);\n    }\n    for (const [event, entries] of Object.entries(settings.hooks)) {\n      if (!Array.isArray(entries)) {\n        throw new Error(`Invalid ${label}: expected hooks.${event} to be an array`);\n      }\n    }\n  }\n\n  return cloneValue(settings);\n}\n\nfunction replacePluginRootPlaceholders(value, pluginRoot) {\n  if (typeof pluginRoot !== 'string') {\n    throw new Error('Invalid Claude plugin root: expected a string');\n  }\n  if (typeof value === 'string') {\n    return value.split(PLUGIN_ROOT_PLACEHOLDER).join(pluginRoot);\n  }","sourceCodeStart":207,"sourceCodeEnd":243,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/claude-settings.js#L207-L243","documentation":"When a Claude settings object contains a \"hooks\" key, its value must itself be a JSON object keyed by hook event names. This error is thrown when hooks exists but is a scalar, array, or null. The library only manages hook maps, so any other shape is rejected.","triggerScenarios":"Calling parseSettings/validatedSettings/validateSettings with a settings object where settings.hooks is not an object — e.g. \"hooks\": true, \"hooks\": [], or \"hooks\": null.","commonSituations":"Hand-merged settings where a hooks list overwrote the hooks map, a tool writing \"hooks\": null to 'disable' hooks, or schema drift from an older Claude settings format.","solutions":["Change settings.hooks to an object mapping event names to arrays","Remove the hooks key entirely if hooks are not used","If hooks were disabled by setting null, use a different mechanism (e.g. remove managed entries) instead","Validate the merged settings JSON with a schema before writing"],"exampleFix":"// before\n{ \"hooks\": null }\n// after\n{ \"hooks\": { \"PreToolUse\": [] } }","handlingStrategy":"type-guard","validationCode":"if ('hooks' in settings && (settings.hooks === null || typeof settings.hooks !== 'object' || Array.isArray(settings.hooks))) throw new Error('hooks must be an object');","typeGuard":"const isHooksMap = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);","tryCatchPattern":"try { return parseSettings(s); } catch (e) { if (e.message.includes('\"hooks\" to be a JSON object')) { const { hooks, ...rest } = s; return parseSettings(rest); } throw e; }","preventionTips":["Never set hooks to null/true to disable hooks — remove the key","Validate merged settings against the Claude settings schema","Use object spreads when merging hook maps, not replacement","Keep a known-good settings.json as reference"],"tags":["validation","settings","hooks"],"backgroundTag":"config-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"}