JuliusBrussee/caveman · error
${path} hooks.${event} must be an array; refusing to overwri
Error message
${path} hooks.${event} must be an array; refusing to overwrite it What it means
Per-event check in assertNativeHooksShape: for each hook event the native integration manages (from nativeHooksDocument for claude/codex/gemini, e.g. UserPromptSubmit), the existing value under hooks.<event> must be an array if present. A non-array (object, string, number) means an incompatible schema that merge would corrupt, so it refuses.
Source
Thrown at packages/cli/src/index.ts:5743
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]) => {
const actual = Array.isArray(hooks[event]) ? hooks[event] as Array<Record<string, unknown>> : [];
const actualEntries = new Set(actual.map((entry) => JSON.stringify(entry)));
return (expectedRaw as Array<Record<string, unknown>>).every((entry) => actualEntries.has(JSON.stringify(entry)));
});
}
function buildCodexEphemeralHome(View on GitHub (pinned to 27d5a3981a)
Solutions
- Rewrite the event entry named in the message as an array: "hooks": { "UserPromptSubmit": [ { ...matcher } ] }
- Remove the malformed event key and re-run setup to regenerate the native entry
- Check the agent's hooks documentation for the array-of-matchers shape and conform existing entries
Example fix
// before
{ "hooks": { "UserPromptSubmit": { "command": "..." } } }
// after
{ "hooks": { "UserPromptSubmit": [ { "hooks": [ { "type": "command", "command": "..." } ] } ] } } Defensive patterns
Strategy: type-guard
Validate before calling
const EVENTS = ["UserPromptSubmit", "Stop", "PostToolUse"]; // events caveman manages
for (const event of EVENTS) {
const v = root.hooks?.[event];
if (v !== undefined && !Array.isArray(v)) {
throw new Error(`${settingsPath}: hooks.${event} must be an array`);
}
} Type guard
function isHookEventArray(v: unknown): v is Array<Record<string, unknown>> {
return Array.isArray(v) && v.every((e) => typeof e === "object" && e !== null && !Array.isArray(e));
} Try / catch
try {
installNativeHooks(agent);
} catch (error) {
if (/hooks\..+ must be an array/.test((error as Error).message)) {
// schema mismatch in user data: repair the named event entry, then retry setup
repairHooksEvent(settingsPath, eventFromMessage(error));
await installNativeHooks(agent);
} else throw error;
} Prevention
- Write each hook event as an array of matcher objects, even for a single hook
- Copy hook snippets from the agent's official docs rather than hand-nesting
- Lint agent settings files in dotfile management (chezmoi/stow) for array shapes
When it happens
Trigger: Installing native hooks when hooks.UserPromptSubmit (or another managed event) holds an object or scalar instead of the array-of-matchers form the agent expects.
Common situations: Settings migrated from a different agent whose hook schema nests by field; user hand-wrote a single hook object instead of a one-element array; older caveman versions used a different shape.
Related errors
- ${path} hooks must be a JSON object; refusing to overwrite i
- 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/381e6ed73f0b24a6.
Report an issue: GitHub.