affaan-m/ECC · error

Invalid : expected a non-empty JSON object

Error message

Invalid ${label}: expected a non-empty JSON object

What it means

validateRecordedManagedHooks validates the recorded (persisted) managed-hooks snapshot, which must be a non-empty plain JSON object. It throws this when the value is not a JSON object (null, array, string, etc.) or is an empty object. This check runs before the looser per-event checks; it guards the ECC bookkeeping that records which hooks it previously installed, so a missing/corrupt snapshot is surfaced loudly rather than silently treated as 'no previous hooks'.

Solutions

  1. Ensure the recorded hooks value is a non-empty plain object keyed by event, e.g. { PreToolUse: [{ id, hooks: [...] }] }.
  2. Check the source file/state store for a missing or emptied recorded-hooks key and restore it (or re-run the install so it is re-recorded).
  3. Fix the JSON parse path that yields null for empty/corrupt files before passing it in.
  4. If there truly are no previous hooks, pass null/omit the value through the API's 'no previous hooks' path rather than an empty object, if supported.

Example fix

// before
const recorded = JSON.parse(fs.readFileSync(stateFile, 'utf8')) ?? {};  // {} throws
// after
const parsed = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && Object.keys(parsed).length > 0) {
  validateRecordedManagedHooks(parsed);
}
Defensive patterns

Strategy: validation

Validate before calling

function canValidateRecordedHooks(v) {
  return v !== null && typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length > 0;
}
if (recorded !== undefined && !canValidateRecordedHooks(recorded)) {
  throw new Error('Recorded hooks snapshot missing or empty — re-run install to re-record');
}

Type guard

function isNonEmptyHooksRecord(v) {
  return v !== null && typeof v === 'object' && !Array.isArray(v)
    && Object.getPrototypeOf(v) !== Object.getPrototypeOf(new Map())
    && Object.keys(v).length > 0;
}

Try / catch

try {
  const prev = findPreviousManagedHooks(state);
} catch (err) {
  if (/expected a non-empty JSON object/.test(err.message)) {
    console.error('Recorded hooks snapshot is missing/empty — the state file may be corrupt; re-run install');
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling validate, findPreviousManagedHooks, previousHooks, or recordedHooks with recorded hooks read as null/undefined (missing field in the store), an empty object {}, a JSON array, or a string blob.

Common situations: A settings/state file truncated or hand-edited so the recorded hooks key was deleted; JSON parse returning null for an empty file; a schema migration that renamed the key, leaving the old lookup undefined; initializing state with {} as a placeholder.

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/e5d867128d2b030e. Report an issue: GitHub.

Appendix: source

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

        );
      }
      if (!Array.isArray(entry.hooks) || entry.hooks.length === 0) {
        throw new Error(
          `Invalid managed hook entry at ${label}.${event}[${index}]: expected hooks`
        );
      }
      entry.hooks.forEach((hook, hookIndex) => {
        validateHookHandler(hook, `${label}.${event}[${index}].hooks[${hookIndex}]`);
      });
    });
  }

  return cloneValue(managedHooks);
}

function validateRecordedManagedHooks(managedHooks, label = 'recorded managed hooks') {
  if (!isJsonObject(managedHooks) || Object.keys(managedHooks).length === 0) {
    throw new Error(`Invalid ${label}: expected a non-empty JSON object`);
  }
  for (const [event, entries] of Object.entries(managedHooks)) {
    if (!isNonEmptyString(event) || !Array.isArray(entries) || entries.length === 0) {
      throw new Error(`Invalid ${label}.${event}: expected a non-empty hook array`);
    }
    const seenIds = new Set();
    entries.forEach((entry, index) => {
      if (!isJsonObject(entry) || !isNonEmptyString(entry.id) || !Array.isArray(entry.hooks)) {
        throw new Error(`Invalid hook entry at ${label}.${event}[${index}]`);
      }
      if (seenIds.has(entry.id)) {
        throw new Error(
          `Invalid ${label}: expected unique id "${entry.id}" within event "${event}"`
        );
      }
      seenIds.add(entry.id);
    });
  }

View on GitHub (pinned to 8321021c54)