{"record":{"id":"90773ada03d269ca","repo":"JuliusBrussee/caveman","slug":"path-hooks-must-be-a-json-object-refusing-to-o","errorCode":null,"errorMessage":"${path} hooks must be a JSON object; refusing to overwrite it","messagePattern":"(.+?) hooks must be a JSON object; refusing to overwrite it","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":6598,"sourceCode":"    }\n    // The lifecycle loop above always writes this event, so the key exists either way.\n    hooks[shrinkEvent] = list;\n  }\n  if (agentId === \"claude\" && includeRecall) {\n    const list = Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit as Array<Record<string, unknown>> : [];\n    const recallCommand = `${cavemanBinForHook()} mem recall-hook`;\n    if (!list.some((entry) => hookEntryCommand(entry) === recallCommand)) {\n      list.push(nativeHookEntry(recallCommand, agentId));\n    }\n    hooks.UserPromptSubmit = list;\n  }\n  root.hooks = hooks;\n  return root;\n}\n\nfunction assertNativeHooksShape(path: string, root: Record<string, unknown>, agentId: \"claude\" | \"codex\" | \"gemini\"): void {\n  if (root.hooks !== undefined && (typeof root.hooks !== \"object\" || root.hooks === null || Array.isArray(root.hooks))) {\n    throw new Error(`${path} hooks must be a JSON object; refusing to overwrite it`);\n  }\n  const hooks = root.hooks as Record<string, unknown> | undefined;\n  if (!hooks) return;\n  // `true` on purpose, unlike the writers: this only reads the event NAMES to\n  // refuse a non-array, so the superset is the safer set to check.\n  const expected = nativeHooksDocument(agentId, true).hooks as Record<string, unknown>;\n  for (const event of Object.keys(expected)) {\n    if (hooks[event] !== undefined && !Array.isArray(hooks[event])) {\n      throw new Error(`${path} hooks.${event} must be an array; refusing to overwrite it`);\n    }\n  }\n}\n\nfunction nativeHookEntriesHealthy(root: Record<string, unknown>, agentId: \"claude\" | \"codex\" | \"gemini\"): boolean {\n  const hooks = root.hooks && typeof root.hooks === \"object\" && !Array.isArray(root.hooks)\n    ? root.hooks as Record<string, unknown>\n    : undefined;\n  if (!hooks) return false;","sourceCodeStart":6580,"sourceCodeEnd":6616,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L6580-L6616","documentation":"Thrown when merging native hooks into an agent config file (settings.json for claude/codex/gemini) whose top-level `hooks` key exists but is not a plain JSON object (string, number, boolean, or array). The CLI refuses to overwrite a value it cannot safely merge, protecting user data.","triggerScenarios":"Calling the hooks-install/setup flow against a config file whose parsed JSON has `hooks` set to a non-object non-null value; detected by assertNativeHooksShape before writing.","commonSituations":"Hand-edited or third-party-tool-written settings.json where hooks was written as a string or array; a corrupted file from a partial write by another tool.","solutions":["Open the config file and inspect the `hooks` key","Change `hooks` to a JSON object (e.g. `{}`) or remove the key so the CLI can populate it","Back up the file before editing: `cp settings.json settings.json.bak`","Re-run the setup/install command"],"exampleFix":"// before (settings.json)\n{ \"hooks\": \"enabled\" }\n// after\n{ \"hooks\": {} }","handlingStrategy":"validation","validationCode":"const doc = JSON.parse(fs.readFileSync(settingsPath, \"utf8\"));\nif (doc.hooks !== undefined && (typeof doc.hooks !== \"object\" || doc.hooks === null || Array.isArray(doc.hooks))) {\n  console.error(`${settingsPath}: hooks must be an object`);\n}","typeGuard":"function isHooksObject(v: unknown): v is Record<string, unknown> {\n  return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  runSetup();\n} catch (e) {\n  if (String(e.message).includes(\"hooks must be a JSON object\")) {\n    console.error(`Fix the hooks key in the named config file to be an object, then retry.`);\n  }\n}","preventionTips":["Keep hooks as a plain object in agent settings files","Validate settings.json with a schema after editing it by hand","Back up config files before running third-party tools that may rewrite them"],"tags":["json","config","hooks","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}