affaan-m/ECC · error

Invalid . : expected at least one hook entry

Error message

Invalid ${label}.${event}: expected at least one hook entry

What it means

Each event's entries array must contain at least one hook entry. validateManagedHooks throws this when an event key exists but maps to an empty array, because an empty event group would produce no hooks and indicates a stale or mis-merged configuration.

Solutions

  1. Remove the empty event key entirely if no hooks are needed for it (or pass hooks without that key).
  2. Re-add at least one valid hook entry under the event.
  3. If hooks were removed intentionally, run the uninstall/remove path instead of validating an empty group.
  4. Regenerate the managed hooks block via the install tooling.

Example fix

// before
const hooks = { PreToolUse: [] };
// after
const hooks = {};  // or add at least one entry: { PreToolUse: [{ id: 'fmt', matcher: 'Bash', hooks: [...] }] }
Defensive patterns

Strategy: validation

Validate before calling

for (const [event, entries] of Object.entries(managedHooks)) {
  if (Array.isArray(entries) && entries.length === 0) delete managedHooks[event];
}

Type guard

function hasNonEmptyEventGroups(v) {
  return v !== null && typeof v === 'object' && !Array.isArray(v)
    && Object.values(v).every(e => Array.isArray(e) && e.length > 0);
}

Try / catch

try {
  desiredHooks(hooks);
} catch (err) {
  if (/expected at least one hook entry/.test(err.message)) {
    const event = err.message.match(/\.(\w+):/)?.[1];
    console.warn(`Event "${event}" has no entries — drop the key or add a hook`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing { PostToolUse: [] } or { SessionEnd: [] } to managedHooks, desiredHooks, expectedHooks, or the other six public callers; deleting all entries under an event during a manual edit but leaving the key.

Common situations: Manually removing the last hook under an event in settings.json; a merge conflict resolution that kept the event key but discarded all entries; programmatic filtering that removed every entry for an event.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

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) => {
      if (!isJsonObject(entry)) {
        throw new Error(
          `Invalid managed hook entry at ${label}.${event}[${index}]: expected a JSON object`
        );
      }
      if (typeof entry.id !== 'string' || entry.id.trim() === '') {
        throw new Error(
          `Invalid managed hook entry at ${label}.${event}[${index}]: `
          + 'expected a non-empty unique id'
        );
      }
      if (seenIds.has(entry.id)) {
        throw new Error(`Invalid ${label}: expected globally unique id "${entry.id}"`);
      }
      seenIds.add(entry.id);

View on GitHub (pinned to 8321021c54)