affaan-m/ECC · error

Invalid hooks config: expected a JSON object with a hooks…

Error message

Invalid hooks config: expected a JSON object with a hooks object

What it means

materializeManagedHooks turns a hooks config (as found in hooks.json) into concrete managed hooks for a target root. It requires the config to be a JSON object containing a hooks object; anything else cannot be materialized, so this error is thrown up front. This catches wrong files or wrong config shapes before path substitution and validation run.

Solutions

  1. Pass the parsed hooks.json object which has a top-level hooks object
  2. If you have raw text, JSON.parse it first and confirm a hooks key exists
  3. Verify the file path points to hooks.json, not settings.json
  4. Add a pre-check: config && typeof config.hooks === 'object'

Example fix

// before
materializeManagedHooks(JSON.parse(await fs.readFile('hooks.json', 'utf8')).hooks, root)
// after
const cfg = JSON.parse(await fs.readFile('hooks.json', 'utf8'));
materializeManagedHooks(cfg, root) // pass the wrapper, not cfg.hooks
Defensive patterns

Strategy: validation

Validate before calling

const isHooksConfig = (c) => c && typeof c === 'object' && !Array.isArray(c) && c.hooks && typeof c.hooks === 'object' && !Array.isArray(c.hooks);

Type guard

const hasHooksObject = (v) => typeof v === 'object' && v !== null && isJsonObject(v.hooks);

Try / catch

try { return materializeManagedHooks(cfg, root); } catch (e) { if (e.message.includes('expected a JSON object with a hooks object')) { console.error('Passed value is not a hooks.json wrapper; check the file path'); } throw e; }

Prevention

When it happens

Trigger: Calling materializeManagedHooks with hooksConfig that is not an object, or whose .hooks property is missing or not an object — e.g. passing the parsed settings.json instead of hooks.json, or a hooks.json without a top-level "hooks" key.

Common situations: Pointing the installer at the wrong JSON file, a plugin's hooks.json missing the hooks wrapper after a bad edit, or passing a string of raw JSON instead of a parsed object.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/a6612fbdd8a9c415. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/install/claude-settings.js:299

        ...entry,
        hooks: entry.hooks.map(hook => ({
          ...hook,
          ...(typeof hook.command === 'string' || Array.isArray(hook.command)
            ? {
              command: Array.isArray(hook.command)
                ? hook.command.map(resolveCommand)
                : resolveCommand(hook.command),
            }
            : {}),
        })),
      })),
    ])
  );
}

function materializeManagedHooks(hooksConfig, targetRoot) {
  if (!isJsonObject(hooksConfig) || !isJsonObject(hooksConfig.hooks)) {
    throw new Error('Invalid hooks config: expected a JSON object with a hooks object');
  }
  if (!isNonEmptyString(targetRoot)) {
    throw new Error('Invalid Claude target root: expected a non-empty string');
  }
  return validateManagedHooks(resolveManagedHookCommands(
    replacePluginRootPlaceholders(hooksConfig.hooks, targetRoot),
    targetRoot
  ));
}

function parseSettings(rawSettings, label = 'Claude settings') {
  let settings;
  try {
    settings = JSON.parse(rawSettings);
  } catch (error) {
    throw new Error(`Failed to parse ${label}: ${error.message}`, { cause: error });
  }
  return validateSettings(settings, label);

View on GitHub (pinned to 8321021c54)