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

  1. Rewrite the event entry named in the message as an array: "hooks": { "UserPromptSubmit": [ { ...matcher } ] }
  2. Remove the malformed event key and re-run setup to regenerate the native entry
  3. 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

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


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/381e6ed73f0b24a6. Report an issue: GitHub.