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
- Read the error message: it prints allowed: sorted(HOOK_REGISTRY) — use one of those exact strings.
- Check action spelling and case (actions are snake_case, e.g. block_resources, set_headers, add_cookies, scroll_to_bottom, wait_for_timeout).
- 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
- Use action strings verbatim from HOOK_REGISTRY keys; snake_case.
- Pin the deployed image version and author configs against its registry.
- Validate specs against HOOK_REGISTRY in CI before deploy.
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
- too many hooks (max 10)
- invalid params for hook '{action}': {e}
- Invalid source(s): {invalid}. Valid: {VALID_SOURCES}
- Hook '{hook_name}' must be a callable function, got {type(ho
- unsupported resource_types {bad}; allowed: {sorted(_ALLOWED_
AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14).
Data as JSON: /api/errors/e335a594dfc4e8ef.
Report an issue: GitHub.