{"record":{"id":"381e6ed73f0b24a6","repo":"JuliusBrussee/caveman","slug":"path-hooks-event-must-be-an-array-refusing","errorCode":null,"errorMessage":"${path} hooks.${event} must be an array; refusing to overwrite it","messagePattern":"(.+?) hooks\\.(.+?) must be an array; refusing to overwrite it","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":6607,"sourceCode":"    }\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;\n  const expected = nativeHooksDocument(agentId, nativeShrinkEnabled()).hooks as Record<string, unknown>;\n  const required = Object.entries(expected).every(([event, expectedRaw]) => {\n    const actual = Array.isArray(hooks[event]) ? hooks[event] as Array<Record<string, unknown>> : [];\n    const actualEntries = new Set(actual.map(canonicalManagedHookEntry).filter(Boolean));\n    return (expectedRaw as Array<Record<string, unknown>>).every((entry) => {\n      const canonical = canonicalManagedHookEntry(entry);\n      return canonical !== undefined && actualEntries.has(canonical);\n    });\n  });","sourceCodeStart":6589,"sourceCodeEnd":6625,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L6589-L6625","documentation":"Thrown when an agent config file defines `hooks.<event>` as a non-array value for an event name the CLI needs to manage (SessionStart, Stop, etc.). Hooks events must map to arrays of matchers; the CLI refuses to overwrite a malformed event value rather than destroying user config.","triggerScenarios":"assertNativeHooksShape iterates the expected event names from nativeHooksDocument(agentId, true) and finds hooks[event] defined but not an array.","commonSituations":"Config produced by another tool or hand edit where hooks.PreToolUse is an object or string instead of the expected array; schema drift between tool versions.","solutions":["Open the config file and find `hooks.<event>` with the non-array value","Wrap the value in an array or convert an object of matchers into the expected array form, or delete the key to let the CLI regenerate it","Validate the resulting JSON parses and hooks events are all arrays","Re-run the setup command"],"exampleFix":"// before\n{ \"hooks\": { \"SessionStart\": { \"command\": \"x\" } } }\n// after\n{ \"hooks\": { \"SessionStart\": [ { \"hooks\": [ { \"type\": \"command\", \"command\": \"x\" } ] } ] } }","handlingStrategy":"validation","validationCode":"const doc = JSON.parse(fs.readFileSync(settingsPath, \"utf8\"));\nfor (const [event, value] of Object.entries(doc.hooks ?? {})) {\n  if (value !== undefined && !Array.isArray(value)) console.error(`hooks.${event} must be an array`);\n}","typeGuard":"function isHookEventArray(v: unknown): v is unknown[] {\n  return Array.isArray(v);\n}","tryCatchPattern":"try {\n  runSetup();\n} catch (e) {\n  if (String(e.message).includes(\"must be an array\")) {\n    console.error(`Convert the named hooks event value to an array in the config file, then retry.`);\n  }\n}","preventionTips":["Follow the documented hooks schema: each event maps to an array of matchers","Run a JSON schema check on settings files after manual edits","Regenerate hooks config from templates instead of hand-writing structures"],"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"}