affaan-m/ECC · error

Invalid managed hook handler at

Error message

Invalid managed hook handler at ${label}: invalid timeout

What it means

validateHookHandler allows an optional 'timeout' on a hook handler but requires it to be a non-negative number. A timeout that is a string ("30"), negative, or otherwise non-numeric fails this check, preventing invalid hook config from being written into Claude settings.

Solutions

  1. Change the timeout at the labeled location to a non-negative number (e.g. 5000, not "5000").
  2. Remove the timeout field entirely if you want the default behavior.
  3. Ensure any config generator emits numeric timeouts.

Example fix

// before
{ "type": "command", "command": "node h.js", "timeout": "5000" }
// after
{ "type": "command", "command": "node h.js", "timeout": 5000 }
Defensive patterns

Strategy: type-guard

Validate before calling

const hasValidTimeout = (h) => h.timeout === undefined || (typeof h.timeout === 'number' && h.timeout >= 0);
for (const handlers of Object.values(settings.hooks ?? {})) {
  for (const h of handlers) if (!hasValidTimeout(h)) throw new Error('timeout must be a non-negative number');
}

Type guard

const isValidTimeout = (t) => t === undefined || (typeof t === 'number' && t >= 0);

Try / catch

try {
  validateManagedHooks(hooks);
} catch (e) {
  if (e.message.includes('invalid timeout')) {
    console.error('Coerce timeout to a non-negative number at:', e.message.match(/at (.+?):/)?.[1]);
  } else throw e;
}

Prevention

When it happens

Trigger: validateManagedHooks processes a handler where hook.timeout is defined but fails typeof hook.timeout === 'number' && hook.timeout >= 0 — e.g. "timeout": "5000" (string) or "timeout": -1.

Common situations: Hand-editing settings and quoting the timeout value; copying configs where timeouts are expressed in seconds as strings; a negative value from a misconfigured generator.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

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

  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`);
    }
    return;
  }

  if (hook.async !== undefined) {
    throw new Error(`Invalid managed hook handler at ${label}: async requires command type`);

View on GitHub (pinned to 8321021c54)