affaan-m/ECC · error

Invalid : expected a JSON object

Error message

Invalid ${label}: expected a JSON object

What it means

validateManagedHooks expects the managed hooks config itself to be a plain JSON object mapping hook event names to entry arrays. This error is thrown when the value passed (under the given label, e.g. 'managed hooks', 'previous managed hooks') is null, an array, a primitive, a Map, or a class instance instead of a plain object.

Solutions

  1. Pass a plain object, e.g. {"PreToolUse":[{...},{...}]} — default {} is not enough; use an object with at least one event (see 319).
  2. JSON.parse file contents before validating; never pass the raw string.
  3. If hooks may be absent, pass {} only when the caller tolerates an empty set; otherwise supply real hook events.
  4. Normalize config loaders to return plain objects (JSON.parse or Object.fromEntries(map)).

Example fix

// before
const hooks = fs.readFileSync(path, 'utf8');
validateManagedHooks(hooks);
// after
const settings = JSON.parse(fs.readFileSync(path, 'utf8'));
validateManagedHooks(settings.hooks ?? {});
Defensive patterns

Strategy: type-guard

Validate before calling

const hooks = settings && typeof settings === 'object' && !Array.isArray(settings) ? settings.hooks : null; if (!isPlainObject(hooks)) throw new Error('settings.hooks must be a plain object');

Type guard

const isHooksConfig = (v) => v !== null && typeof v === 'object' && !Array.isArray(v) && (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);

Try / catch

try { installHooks(rawHooks); } catch (e) { if (e.message.includes('expected a JSON object')) { console.error('Managed hooks config must be a parsed plain object:', e.message); process.exitCode = 1; } else throw e; }

Prevention

When it happens

Trigger: Calling managedHooks/materializeManagedHooks/desiredHooks/expectedHooks or the settings APIs that call validateManagedHooks with undefined (settings file missing hooks key), an array of hooks, a JSON string not yet parsed, or a Map.

Common situations: Reading settings.json where the 'hooks' key is absent (undefined); passing the raw file contents string instead of the parsed object; config loaders returning Maps or objects with non-plain prototypes; accidentally passing the whole settings document instead of its hooks section.

Related errors


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

Appendix: source

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

      hook.allowedEnvVars !== undefined
      && (!Array.isArray(hook.allowedEnvVars)
        || !hook.allowedEnvVars.every(isNonEmptyString))
    ) {
      throw new Error(`Invalid managed hook handler at ${label}: invalid allowedEnvVars`);
    }
    return;
  }
  if (!isNonEmptyString(hook.prompt)) {
    throw new Error(`Invalid managed hook handler at ${label}: invalid prompt`);
  }
  if (hook.model !== undefined && !isNonEmptyString(hook.model)) {
    throw new Error(`Invalid managed hook handler at ${label}: invalid model`);
  }
}

function validateManagedHooks(managedHooks, label = 'managed hooks') {
  if (!isJsonObject(managedHooks)) {
    throw new Error(`Invalid ${label}: expected a JSON object`);
  }
  if (Object.keys(managedHooks).length === 0) {
    throw new Error(`Invalid ${label}: expected at least one hook event`);
  }

  const seenIds = new Set();
  for (const [event, entries] of Object.entries(managedHooks)) {
    if (!VALID_EVENTS.has(event)) {
      throw new Error(`Invalid ${label}: unsupported hook event "${event}"`);
    }
    if (!Array.isArray(entries)) {
      throw new Error(`Invalid ${label}.${event}: expected an array`);
    }
    if (entries.length === 0) {
      throw new Error(`Invalid ${label}.${event}: expected at least one hook entry`);
    }

    entries.forEach((entry, index) => {

View on GitHub (pinned to 8321021c54)