unclecode/crawl4ai · error · ValueError
Hook '{hook_name}' must be a callable function, got {type(ho
Error message
Hook '{hook_name}' must be a callable function, got {type(hook_func)} What it means
hooks_to_string (crawl4ai/utils.py:3726) raises ValueError when a value in the hooks dict passed to it is not callable. The function serializes hook functions into source-code strings for the Docker/remote API; before extracting source with inspect.getsource it asserts each hook is a function. Passing a string of code, a module, None, or an already-serialized source string triggers this.
Source
Thrown at crawl4ai/utils.py:3726
Dictionary mapping hook point names to string representations of the functions.
Example:
>>> async def my_hook(page, context, **kwargs):
... await page.set_viewport_size({"width": 1920, "height": 1080})
... return page
>>>
>>> hooks_dict = {"on_page_context_created": my_hook}
>>> api_hooks = hooks_to_string(hooks_dict)
>>> # api_hooks is now ready to use with Docker API
Raises:
ValueError: If a hook is not callable or source cannot be extracted
"""
result = {}
for hook_name, hook_func in hooks.items():
if not callable(hook_func):
raise ValueError(f"Hook '{hook_name}' must be a callable function, got {type(hook_func)}")
try:
# Get the source code of the function
source = inspect.getsource(hook_func)
# Remove any leading indentation to get clean source
source = textwrap.dedent(source)
result[hook_name] = source
except (OSError, TypeError) as e:
raise ValueError(
f"Cannot extract source code for hook '{hook_name}'. "
f"Make sure the function is defined in a file (not interactively). Error: {e}"
)
return result
View on GitHub (pinned to 7e80152142)
Solutions
- Pass actual function objects, not source strings: define def my_hook(ctx): ... and use hooks_to_string({"on_page_context_created": my_hook}).
- If config arrives as JSON strings, exec/compile them into functions first (only for trusted input) or use the server's declarative hook specs instead.
- Add a unit assertion callable(h) for every hook before calling hooks_to_string to fail fast with your own message.
Example fix
# before
api_hooks = hooks_to_string({"on_page_context_created": "my_hook_source"})
# after
def my_hook(page, context, **kwargs):
return None
api_hooks = hooks_to_string({"on_page_context_created": my_hook}) Defensive patterns
Strategy: type-guard
Type guard
from typing import Callable
def is_valid_hook_dict(hooks: dict) -> bool:
return bool(hooks) and all(
isinstance(k, str) and callable(v) and not isinstance(v, (str, bytes))
for k, v in hooks.items()
) Try / catch
try:
api_hooks = hooks_to_string(hooks)
except ValueError as e:
if "must be a callable" in str(e):
hooks = {k: v for k, v in hooks.items() if callable(v)} # drop bad entries
raise Prevention
- Always define hooks as named module-level functions
- Assert callable() on config-loaded hook values before serialization
When it happens
Trigger: Calling hooks_to_string({"on_page_context_created": "def hook(ctx): ..."}) — passing source text instead of a function object; passing a class or builtin; a dict built from JSON config where values are strings by construction.
Common situations: Porting a JSON/YAML hook config to the Python API and forgetting to define actual functions; passing hook names instead of hook callables; refactoring that replaces functions with functools.partial of a non-callable.
Related errors
- Cannot extract source code for hook '{hook_name}'. Make sure
- unsupported resource_types {bad}; allowed: {sorted(_ALLOWED_
- too many headers (max {_MAX_HEADERS})
- invalid header name {name!r}
- control characters in value for header {name!r}
AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14).
Data as JSON: /api/errors/253213ab6e8aa2fb.
Report an issue: GitHub.