affaan-m/ECC · error

Invalid managed hook entry at

Error message

Invalid managed hook entry at ${label}.${event}[${index}]: expected hooks

What it means

Every hook entry must have a hooks array containing at least one handler definition. validateManagedHooks throws this when entry.hooks is missing, not an array, or an empty array. The hooks array holds the actual handler objects (command/http/prompt/agent) that Claude Code executes.

Solutions

  1. Ensure each entry has hooks: [ ... ] with at least one handler object, e.g. { type: 'command', command: 'npx lint' }.
  2. Wrap a single handler object in an array.
  3. Locate the offending entry via the index in the message.
  4. If the handler was intentionally removed, delete the whole entry instead of leaving an empty hooks array.

Example fix

// before
{ id: 'lint', matcher: 'Bash', hooks: [] }
// after
{ id: 'lint', matcher: 'Bash', hooks: [{ type: 'command', command: 'npm run lint' }] }
Defensive patterns

Strategy: validation

Validate before calling

for (const entries of Object.values(managedHooks)) {
  entries.forEach((e, i) => {
    if (!Array.isArray(e.hooks) || e.hooks.length === 0) {
      throw new Error(`Entry ${i} (${e.id}) must have a non-empty hooks array`);
    }
  });
}

Type guard

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

Try / catch

try {
  materializeManagedHooks(hooks);
} catch (err) {
  if (/expected hooks/.test(err.message)) {
    console.error('Each entry needs hooks: [{ type: "command", command: "..." }] with at least one handler');
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing { id: 'x', matcher: 'Bash' } with no hooks field; hooks: []; hooks: 'cmd' (a string instead of an array).

Common situations: Hand-writing an entry and forgetting the hooks wrapper; removing all handlers from an entry during cleanup; a template that emits a singular hook object instead of an array; copying Claude-native settings where a flat handler replaced the array.

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

Appendix: source

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

      if (
        !Object.prototype.hasOwnProperty.call(entry, 'matcher')
        && !EVENTS_WITHOUT_MATCHER.has(event)
      ) {
        throw new Error(
          `Invalid managed hook entry at ${label}.${event}[${index}]: missing matcher`
        );
      }
      if (
        Object.prototype.hasOwnProperty.call(entry, 'matcher')
        && typeof entry.matcher !== 'string'
        && !isJsonObject(entry.matcher)
      ) {
        throw new Error(
          `Invalid managed hook entry at ${label}.${event}[${index}]: invalid matcher`
        );
      }
      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) {

View on GitHub (pinned to 8321021c54)