{"record":{"id":"a01640d24da6597c","repo":"affaan-m/ECC","slug":"invalid-label-expected-a-json-object","errorCode":null,"errorMessage":"Invalid ${label}: expected a JSON object","messagePattern":"Invalid (.+?): expected a JSON object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/claude-settings.js","lineNumber":129,"sourceCode":"      hook.allowedEnvVars !== undefined\n      && (!Array.isArray(hook.allowedEnvVars)\n        || !hook.allowedEnvVars.every(isNonEmptyString))\n    ) {\n      throw new Error(`Invalid managed hook handler at ${label}: invalid allowedEnvVars`);\n    }\n    return;\n  }\n  if (!isNonEmptyString(hook.prompt)) {\n    throw new Error(`Invalid managed hook handler at ${label}: invalid prompt`);\n  }\n  if (hook.model !== undefined && !isNonEmptyString(hook.model)) {\n    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) => {","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/claude-settings.js#L111-L147","documentation":"validateManagedHooks expects the managed hooks config itself to be a plain JSON object mapping hook event names to entry arrays. This error is thrown when the value passed (under the given label, e.g. 'managed hooks', 'previous managed hooks') is null, an array, a primitive, a Map, or a class instance instead of a plain object.","triggerScenarios":"Calling managedHooks/materializeManagedHooks/desiredHooks/expectedHooks or the settings APIs that call validateManagedHooks with undefined (settings file missing hooks key), an array of hooks, a JSON string not yet parsed, or a Map.","commonSituations":"Reading settings.json where the 'hooks' key is absent (undefined); passing the raw file contents string instead of the parsed object; config loaders returning Maps or objects with non-plain prototypes; accidentally passing the whole settings document instead of its hooks section.","solutions":["Pass a plain object, e.g. {\"PreToolUse\":[{...},{...}]} — default {} is not enough; use an object with at least one event (see 319).","JSON.parse file contents before validating; never pass the raw string.","If hooks may be absent, pass {} only when the caller tolerates an empty set; otherwise supply real hook events.","Normalize config loaders to return plain objects (JSON.parse or Object.fromEntries(map))."],"exampleFix":"// before\nconst hooks = fs.readFileSync(path, 'utf8');\nvalidateManagedHooks(hooks);\n// after\nconst settings = JSON.parse(fs.readFileSync(path, 'utf8'));\nvalidateManagedHooks(settings.hooks ?? {});","handlingStrategy":"type-guard","validationCode":"const hooks = settings && typeof settings === 'object' && !Array.isArray(settings) ? settings.hooks : null; if (!isPlainObject(hooks)) throw new Error('settings.hooks must be a plain object');","typeGuard":"const isHooksConfig = (v) => v !== null && typeof v === 'object' && !Array.isArray(v) && (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);","tryCatchPattern":"try { installHooks(rawHooks); } catch (e) { if (e.message.includes('expected a JSON object')) { console.error('Managed hooks config must be a parsed plain object:', e.message); process.exitCode = 1; } else throw e; }","preventionTips":["JSON.parse file contents before passing them.","Pass settings.hooks, not the whole settings document.","Avoid Maps/class instances; use Object.fromEntries if needed."],"tags":["config","validation","types"],"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"}