JuliusBrussee/caveman · error
${path} hooks must be a JSON object; refusing to overwrite i
Error message
${path} hooks must be a JSON object; refusing to overwrite it What it means
assertNativeHooksShape validates an agent settings JSON (~/.claude/settings.json, codex/gemini equivalents) before native hooks are merged. If a "hooks" key exists and is not a plain JSON object (it is an array, a string, a number, or null), the merge would destroy user data, so the CLI refuses to overwrite it.
Source
Thrown at packages/cli/src/index.ts:5736
: nativeHookEntry(shrinkCommand));
}
hooks[shrinkEvent] = list;
}
if (agentId === "claude" && includeRecall) {
const list = Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit as Array<Record<string, unknown>> : [];
const recallCommand = `${cavemanBinForHook()} mem recall-hook`;
if (!list.some((entry) => hookEntryCommand(entry) === recallCommand)) {
list.push(nativeHookEntry(recallCommand));
}
hooks.UserPromptSubmit = list;
}
root.hooks = hooks;
return root;
}
function assertNativeHooksShape(path: string, root: Record<string, unknown>, agentId: "claude" | "codex" | "gemini"): void {
if (root.hooks !== undefined && (typeof root.hooks !== "object" || root.hooks === null || Array.isArray(root.hooks))) {
throw new Error(`${path} hooks must be a JSON object; refusing to overwrite it`);
}
const hooks = root.hooks as Record<string, unknown> | undefined;
if (!hooks) return;
const expected = nativeHooksDocument(agentId, true).hooks as Record<string, unknown>;
for (const event of Object.keys(expected)) {
if (hooks[event] !== undefined && !Array.isArray(hooks[event])) {
throw new Error(`${path} hooks.${event} must be an array; refusing to overwrite it`);
}
}
}
function nativeHookEntriesHealthy(root: Record<string, unknown>, agentId: "claude" | "codex" | "gemini"): boolean {
const hooks = root.hooks && typeof root.hooks === "object" && !Array.isArray(root.hooks)
? root.hooks as Record<string, unknown>
: undefined;
if (!hooks) return false;
const expected = nativeHooksDocument(agentId, true).hooks as Record<string, unknown>;
return Object.entries(expected).every(([event, expectedRaw]) => {View on GitHub (pinned to 27d5a3981a)
Solutions
- Edit the settings file named in the message so "hooks" is an object, e.g. {"UserPromptSubmit": []}, moving any list entries under the correct event key
- Back up and remove the malformed "hooks" key entirely, then re-run setup to have caveman create it
- Validate the file with `jq '.hooks | type'` expecting "object" before setup
Example fix
// before (.claude/settings.json)
{ "hooks": [ { "UserPromptSubmit": [] } ] }
// after
{ "hooks": { "UserPromptSubmit": [] } } Defensive patterns
Strategy: type-guard
Validate before calling
import { readFileSync } from "node:fs";
const root = JSON.parse(readFileSync(settingsPath, "utf8"));
if (root.hooks !== undefined && (typeof root.hooks !== "object" || root.hooks === null || Array.isArray(root.hooks))) {
throw new Error(`${settingsPath}: fix hooks to an object before running caveman setup`);
} Type guard
function isHooksObject(v: unknown): v is Record<string, unknown> {
return typeof v === "object" && v !== null && !Array.isArray(v);
} Try / catch
try {
installNativeHooks(agent);
} catch (error) {
if (/hooks must be a JSON object/.test((error as Error).message)) {
// refuse-to-overwrite guard: repair the file manually; do not force
promptUserToFix(settingsPathFromMessage(error));
} else throw error;
} Prevention
- Keep "hooks" as an object keyed by event names in agent settings files
- Validate settings with `jq '.hooks | type' == "object"` before setup
- Never let external tools write arrays at the hooks key
When it happens
Trigger: Running `caveman setup --agent-native <agent>` (or hooks installation) when the agent's settings file has "hooks" set to an array/string/number/null instead of an object mapping event names to arrays.
Common situations: Hand-edited settings file where hooks was written as a list; migration from a tool that stored hooks differently; malformed merge by another installer.
Related errors
- ${path} hooks.${event} must be an array; refusing to overwri
- caveman agent: Standard Schema cannot emit draft-07 input JS
- caveman agent: Standard Schema needs inputJSONSchema or Stan
- caveman agent: Standard Schema emitted invalid input JSON Sc
- ${settingsPath} env must be a JSON object; refusing to overw
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/90773ada03d269ca.
Report an issue: GitHub.