{"record":{"id":"e5d867128d2b030e","repo":"affaan-m/ECC","slug":"invalid-label-expected-a-non-empty-json-object","errorCode":null,"errorMessage":"Invalid ${label}: expected a non-empty JSON object","messagePattern":"Invalid (.+?): expected a non-empty JSON object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/claude-settings.js","lineNumber":196,"sourceCode":"        );\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') {\n  if (!isJsonObject(managedHooks) || Object.keys(managedHooks).length === 0) {\n    throw new Error(`Invalid ${label}: expected a non-empty JSON object`);\n  }\n  for (const [event, entries] of Object.entries(managedHooks)) {\n    if (!isNonEmptyString(event) || !Array.isArray(entries) || entries.length === 0) {\n      throw new Error(`Invalid ${label}.${event}: expected a non-empty hook array`);\n    }\n    const seenIds = new Set();\n    entries.forEach((entry, index) => {\n      if (!isJsonObject(entry) || !isNonEmptyString(entry.id) || !Array.isArray(entry.hooks)) {\n        throw new Error(`Invalid hook entry at ${label}.${event}[${index}]`);\n      }\n      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  }","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/claude-settings.js#L178-L214","documentation":"validateRecordedManagedHooks validates the recorded (persisted) managed-hooks snapshot, which must be a non-empty plain JSON object. It throws this when the value is not a JSON object (null, array, string, etc.) or is an empty object. This check runs before the looser per-event checks; it guards the ECC bookkeeping that records which hooks it previously installed, so a missing/corrupt snapshot is surfaced loudly rather than silently treated as 'no previous hooks'.","triggerScenarios":"Calling validate, findPreviousManagedHooks, previousHooks, or recordedHooks with recorded hooks read as null/undefined (missing field in the store), an empty object {}, a JSON array, or a string blob.","commonSituations":"A settings/state file truncated or hand-edited so the recorded hooks key was deleted; JSON parse returning null for an empty file; a schema migration that renamed the key, leaving the old lookup undefined; initializing state with {} as a placeholder.","solutions":["Ensure the recorded hooks value is a non-empty plain object keyed by event, e.g. { PreToolUse: [{ id, hooks: [...] }] }.","Check the source file/state store for a missing or emptied recorded-hooks key and restore it (or re-run the install so it is re-recorded).","Fix the JSON parse path that yields null for empty/corrupt files before passing it in.","If there truly are no previous hooks, pass null/omit the value through the API's 'no previous hooks' path rather than an empty object, if supported."],"exampleFix":"// before\nconst recorded = JSON.parse(fs.readFileSync(stateFile, 'utf8')) ?? {};  // {} throws\n// after\nconst parsed = JSON.parse(fs.readFileSync(stateFile, 'utf8'));\nif (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && Object.keys(parsed).length > 0) {\n  validateRecordedManagedHooks(parsed);\n}\n","handlingStrategy":"validation","validationCode":"function canValidateRecordedHooks(v) {\n  return v !== null && typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length > 0;\n}\nif (recorded !== undefined && !canValidateRecordedHooks(recorded)) {\n  throw new Error('Recorded hooks snapshot missing or empty — re-run install to re-record');\n}","typeGuard":"function isNonEmptyHooksRecord(v) {\n  return v !== null && typeof v === 'object' && !Array.isArray(v)\n    && Object.getPrototypeOf(v) !== Object.getPrototypeOf(new Map())\n    && Object.keys(v).length > 0;\n}","tryCatchPattern":"try {\n  const prev = findPreviousManagedHooks(state);\n} catch (err) {\n  if (/expected a non-empty JSON object/.test(err.message)) {\n    console.error('Recorded hooks snapshot is missing/empty — the state file may be corrupt; re-run install');\n  }\n  throw err;\n}","preventionTips":["Guard JSON.parse results for null before storing the snapshot","Never initialize recorded-hooks state with {} — use null/undefined for 'no previous hooks'","Back up or validate the state file before migrations that rename keys","Re-record the snapshot via a fresh install if the store is corrupted"],"tags":["validation","state","hooks"],"backgroundTag":"schema-validation-failed","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"}