unclecode/crawl4ai · error · HookValidationError

unknown hook action {action!r}; allowed: {sorted(HOOK_REGIST

Error message

unknown hook action {action!r}; allowed: {sorted(HOOK_REGISTRY)}

What it means

build_declarative_hooks raises HookValidationError('unknown hook action ...') when a spec's action string is not a key in HOOK_REGISTRY. The message includes the requested action and the sorted list of registered actions, so the supported vocabulary is self-describing.

Source

Thrown at deploy/docker/hook_registry.py:213

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] = {}
    for hook_point, sub_hooks in grouped.items():
        def _compose(sub_hooks):
            async def composed(page, **kwargs):
                for fn in sub_hooks:
                    await fn(page, **kwargs)
                return page
            return composed
        hooks[hook_point] = _compose(sub_hooks)

View on GitHub (pinned to 7e80152142)

Solutions

  1. Read the error message: it prints allowed: sorted(HOOK_REGISTRY) — use one of those exact strings.
  2. Check action spelling and case (actions are snake_case, e.g. block_resources, set_headers, add_cookies, scroll_to_bottom, wait_for_timeout).
  3. Confirm the deployed image version matches the docs/API you authored the config against.

Example fix

# before
{"action": "blockResources", "params": {...}}

# after
{"action": "block_resources", "params": {...}}
Defensive patterns

Strategy: validation

Validate before calling

from hook_registry import HOOK_REGISTRY

def valid_actions(specs) -> bool:
    return all(
        (s.get("action") if isinstance(s, dict) else getattr(s, "action", None)) in HOOK_REGISTRY
        for s in (specs or [])
    )

Type guard

def is_known_hook_action(action) -> bool:
    return isinstance(action, str) and action in HOOK_REGISTRY

Try / catch

try:
    hooks = build_declarative_hooks(specs)
except HookValidationError as e:
    # message includes allowed: sorted(HOOK_REGISTRY)
    raise ConfigError(str(e)) from e

Prevention

When it happens

Trigger: Sending {"action": "wait_for_selector", ...} or {"action": "BlockResources", ...} (wrong case) or a spec missing the action key (action=None) when HOOK_REGISTRY has no such entry.

Common situations: Version drift: the action existed in an older/newer deploy but not this one; case or naming mismatches (snake_case vs CamelCase); typos; specs authored against different hook registry forks.

Related errors


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