affaan-m/ECC · error

Invalid managed hook handler at

Error message

Invalid managed hook handler at ${label}: expected a JSON object

What it means

validateHookHandler checks each managed hook handler against the expected Claude hook-handler schema. A handler that is not a JSON object (string, array, number, null) fails the very first check. The label in the message identifies where in the hooks structure the bad value sits.

Solutions

  1. Rewrite the handler at the labeled location as a JSON object with a valid 'type' field.
  2. Validate the settings JSON structure (jq or a schema check) before running the install.
  3. Let the ECC installer generate hook entries instead of hand-authoring them.

Example fix

// before
"hooks": { "PreToolUse": ["node my-hook.js"] }
// after
"hooks": { "PreToolUse": [{ "type": "command", "command": "node my-hook.js" }] }
Defensive patterns

Strategy: validation

Validate before calling

const isHookHandlerObject = (h) => typeof h === 'object' && h !== null && !Array.isArray(h);
for (const handlers of Object.values(settings.hooks ?? {})) {
  for (const h of handlers) if (!isHookHandlerObject(h)) throw new Error('hook handler must be an object');
}

Type guard

const isJsonObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);

Try / catch

try {
  validateManagedHooks(hooks);
} catch (e) {
  if (e.message.includes('expected a JSON object')) {
    console.error('Fix the hook handler shape at:', e.message.match(/at (.+?):/)?.[1]);
  } else throw e;
}

Prevention

When it happens

Trigger: validateManagedHooks encounters a hook handler value that is not a plain object — e.g. a hook entry defined as a string command instead of an object with type/command fields.

Common situations: Hand-written settings.json where a hook was written as "node hook.js" instead of {"type":"command","command":"node hook.js"}; JSON produced by another tool using a different hook schema.

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

Appendix: source

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

  return path.join(targetRoot, CLAUDE_SETTINGS_FILENAME);
}

function assertClaudeSettingsPath(destinationPath, trustedRoot) {
  const resolvedDestination = path.resolve(destinationPath);
  const resolvedExpected = path.resolve(getClaudeSettingsPath(trustedRoot));
  const pathsMatch = process.platform === 'win32'
    ? resolvedDestination.toLowerCase() === resolvedExpected.toLowerCase()
    : resolvedDestination === resolvedExpected;
  if (!pathsMatch) {
    throw new Error(
      `Refusing to manage Claude hooks outside the canonical settings file: ${destinationPath}`
    );
  }
}

function validateHookHandler(hook, label) {
  if (!isJsonObject(hook)) {
    throw new Error(`Invalid managed hook handler at ${label}: expected a JSON object`);
  }
  if (!VALID_HOOK_TYPES.has(hook.type)) {
    throw new Error(`Invalid managed hook handler at ${label}: unsupported type`);
  }
  if (hook.timeout !== undefined && (typeof hook.timeout !== 'number' || hook.timeout < 0)) {
    throw new Error(`Invalid managed hook handler at ${label}: invalid timeout`);
  }

  if (hook.type === 'command') {
    const validCommand = isNonEmptyString(hook.command)
      || (Array.isArray(hook.command)
        && hook.command.length > 0
        && hook.command.every(isNonEmptyString));
    if (!validCommand) {
      throw new Error(`Invalid managed hook handler at ${label}: invalid command`);
    }
    if (hook.async !== undefined && typeof hook.async !== 'boolean') {
      throw new Error(`Invalid managed hook handler at ${label}: invalid async flag`);

View on GitHub (pinned to 8321021c54)