deepset-ai/haystack · error · TypeError
Hook registered for hook point '{hook_point}' must have a ca
Error message
Hook registered for hook point '{hook_point}' must have a callable 'run(state)', got an object of type '{type(h).__name__}'. What it means
FileSystemSkillStore.warm_up() scans self.skills_dir to build the skill catalog; before the first scan it verifies the configured path exists and is a directory. This ValueError is raised when skills_dir is missing, a file instead of a directory, or otherwise not listable. warm_up is idempotent and is called lazily by _skill_dir and list_skills, so most store operations fail with this if the directory is bad.
Source
Thrown at haystack/components/agents/agent.py:133
Validate a hooks mapping: known hook points, real Hook objects, and hook-point restrictions.
:param hooks: Mapping of hook point to the hooks registered under it.
:raises ValueError: If a hook point is unknown, or a hook is registered under a hook point it does not support.
:raises TypeError: If a registered hook has no callable `run(state)`.
"""
for hook_point, hook_list in hooks.items():
if hook_point not in VALID_HOOK_POINTS:
raise ValueError(
f"Invalid hook point '{hook_point}'. Valid hook points are: {', '.join(VALID_HOOK_POINTS)}."
)
for h in hook_list:
if not callable(getattr(h, "run", None)):
if callable(h):
raise TypeError(
f"Hook registered for hook point '{hook_point}' is callable but is not a Hook object. "
"If it is a function, wrap it with the @hook decorator."
)
raise TypeError(
f"Hook registered for hook point '{hook_point}' must have a callable 'run(state)', "
f"got an object of type '{type(h).__name__}'."
)
# A hook may declare `allowed_hook_points` to restrict where it can run (e.g. ConfirmationHook only
# makes sense at "before_tool"). Hooks without it can be registered under any hook point.
allowed_points = getattr(h, "allowed_hook_points", None)
if allowed_points is not None and hook_point not in allowed_points:
raise ValueError(
f"Hook of type '{type(h).__name__}' is registered under hook point '{hook_point}' but only "
f"supports: {', '.join(allowed_points)}."
)
def _consume_continue_run(state: State) -> bool:
"""Return the `continue_run` control flag and reset it so it does not carry over to the next exit attempt."""
should_continue = state.data["continue_run"]
state.set("continue_run", False)
return should_continueView on GitHub (pinned to e318778c9b)
Solutions
- Create the directory or correct the skills_dir value passed to FileSystemSkillStore to an existing directory.
- Use an absolute path (Path(...).resolve()) so it doesn't depend on the process working directory.
- Verify with Path(skills_dir).is_dir() before constructing the store, and ensure the path is mounted/copied into containers.
Example fix
// before
store = FileSystemSkillStore(skills_dir="./skills")
// after
from pathlib import Path
skills_dir = Path("./skills").resolve()
if not skills_dir.is_dir():
skills_dir.mkdir(parents=True)
store = FileSystemSkillStore(skills_dir=skills_dir) Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def ensure_skills_dir(skills_dir) -> Path:
path = Path(skills_dir).resolve()
if not path.is_dir():
raise NotADirectoryError(f"skills_dir does not exist or is not a directory: {path}")
return path
store = FileSystemSkillStore(skills_dir=ensure_skills_dir("./skills")) Try / catch
try:
skills = store.list_skills()
except ValueError as e:
if "does not exist or is not a directory" in str(e):
Path(skills_dir).mkdir(parents=True, exist_ok=True)
skills = store.list_skills()
else:
raise Prevention
- Use absolute resolved paths for skills_dir, independent of the working directory
- Create the directory at deployment/startup time (mkdir parents=True, exist_ok=True)
- In containers, ensure the skills volume is mounted before constructing the store
- Check path existence in a startup health check
When it happens
Trigger: Constructing FileSystemSkillStore(skills_dir=...) with a nonexistent path, a file path, a deleted/renamed directory, or an unreachable mount; then calling list_skills(), load_skill(), or any read that triggers warm_up.
Common situations: Typoed or relative path resolved from the wrong working directory; env var/setting pointing at a path that doesn't exist in the deployment container; skills directory removed after startup; path pointing to a zip archive or file.
Related errors
- Variable '{var_name}' from {prompt_source} is already define
- Hook of type '{type(h).__name__}' is registered under hook p
- {type(chat_generator).__name__} does not accept tools parame
- Invalid hook point '{hook_point}'. Valid hook points are: {'
- Hook registered for hook point '{hook_point}' is callable bu
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/32666cd41f22eca8.
Report an issue: GitHub.