unclecode/crawl4ai · error · HookValidationError

too many hooks (max 10)

Error message

too many hooks (max 10)

What it means

build_declarative_hooks raises HookValidationError('too many hooks (max 10)') when the declarative spec list exceeds 10 entries. The cap bounds config size and per-page overhead since every hook runs on each matching page event.

Source

Thrown at deploy/docker/hook_registry.py:205

        "hook_point": "before_retrieve_html",
        "params_model": WaitForTimeoutParams,
        "factory": _factory_wait_for_timeout,
        "description": "Wait a bounded number of milliseconds before retrieving HTML.",
    },
}


def build_declarative_hooks(specs: List[Any]) -> Dict[str, Callable]:
    """Validate declarative hook specs and return {hook_point: composed async hook}.

    Each spec is an object/dict with `action` and `params`. Multiple specs that
    target the same hook point are composed and run in order. Raises
    HookValidationError on an unknown action or invalid params.
    """
    if not specs:
        return {}
    if len(specs) > 10:
        raise HookValidationError("too many hooks (max 10)")

    grouped: Dict[str, List[Callable]] = {}
    for spec in specs:
        action = spec.get("action") if isinstance(spec, dict) else getattr(spec, "action", None)
        raw_params = (spec.get("params", {}) if isinstance(spec, dict) else getattr(spec, "params", {})) or {}
        entry = HOOK_REGISTRY.get(action)
        if entry is None:
            raise HookValidationError(
                f"unknown hook action {action!r}; allowed: {sorted(HOOK_REGISTRY)}"
            )
        try:
            params = entry["params_model"](**raw_params)
        except Exception as e:
            raise HookValidationError(f"invalid params for hook '{action}': {e}")
        sub_hook = entry["factory"](params)
        grouped.setdefault(entry["hook_point"], []).append(sub_hook)

    hooks: Dict[str, Callable] = {}

View on GitHub (pinned to 7e80152142)

Solutions

  1. Consolidate specs: one block_resources spec accepts a list of resource types; one set_headers spec accepts up to 20 headers.
  2. Delete hooks you no longer need rather than commenting them out in generated config.
  3. If the workflow truly needs more, request a raise of the cap or run two crawl jobs.

Example fix

# before
hooks = [{"action": "block_resources", "params": {"resource_types": ["image"]}},
          {"action": "block_resources", "params": {"resource_types": ["font"]}}, ...  # 11 specs

# after
hooks = [{"action": "block_resources", "params": {"resource_types": ["image", "font", "media", "stylesheet"]}}]
Defensive patterns

Strategy: validation

Validate before calling

def valid_hook_count(specs) -> bool:
    return isinstance(specs, list) and len(specs) <= 10

Type guard

def is_within_hook_cap(specs) -> bool:
    return not specs or len(specs) <= 10

Try / catch

try:
    hooks = build_declarative_hooks(specs)
except HookValidationError as e:
    if "too many hooks" in str(e):
        specs = merge_same_action_specs(specs)  # e.g. fold block_resources lists
    else:
        raise

Prevention

When it happens

Trigger: Posting a crawl config whose hooks array contains 11 or more {action, params} specs.

Common situations: Generated configs that enumerate per-domain hooks; accumulating hooks across merges/patches until the list grows past 10; teams unaware the limit counts ALL hooks, not per hook point.

Related errors


AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14). Data as JSON: /api/errors/32fed0f979bcbdee. Report an issue: GitHub.