deepset-ai/haystack · error · ValueError
Hook of type '{type(h).__name__}' is registered under hook p
Error message
Hook of type '{type(h).__name__}' is registered under hook point '{hook_point}' but only supports: {', '.join(allowed_points)}. What it means
During warm_up, every skill .md file's frontmatter must contain a non-empty 'description'. This ValueError is raised when frontmatter.get("description") is missing, None, or an empty string. The description is required because it is surfaced to the model for skill selection.
Source
Thrown at haystack/components/agents/agent.py:141
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_continue
def _get_model_exit_reason(messages: list[ChatMessage]) -> str | None:
"""
Return the exit reason for a terminal assistant reply without tool calls.
Incomplete generation reasons take precedence over text so callers can distinguish a partial response from a
complete answer. An empty response without a recognized terminal reason does not trigger an exit, preserving theView on GitHub (pinned to e318778c9b)
Solutions
- Add a non-empty 'description: ...' line to the skill file's frontmatter.
- If the file isn't meant to be a skill, move it out of skills_dir or into a subdirectory the scanner doesn't treat as a skill .md.
- Check the exact file named in the error message and confirm the key spelling is 'description' (lowercase).
Example fix
// before (SKILL.md) --- name: my-skill --- // after --- name: my-skill description: Explains how to use the my-skill workflow. ---
Defensive patterns
Strategy: validation
Validate before calling
import yaml
from pathlib import Path
def skill_has_description(skill_file: Path) -> bool:
lines = skill_file.read_text(encoding="utf-8").splitlines()
closing = lines.index("---", 1)
fm = yaml.safe_load("\n".join(lines[1:closing])) or {}
return isinstance(fm, dict) and bool(fm.get("description")) Type guard
def has_nonempty_description(frontmatter: dict) -> bool:
return bool(frontmatter.get("description")) Try / catch
try:
store.list_skills()
except ValueError as e:
if "missing a 'description'" in str(e):
logger.error("Add a description to the frontmatter of: %s", e)
raise Prevention
- Start every new skill from a template that includes name and description
- Add a CI test asserting every skills/**/*.md frontmatter has a non-empty description
- Never remove or rename the 'description' key when editing skills
When it happens
Trigger: A skill markdown file in skills_dir whose YAML frontmatter lacks a 'description' key, has description: (empty/null), or description: "" ; triggered by warm_up via list_skills/load_skill or store construction-time warming.
Common situations: Authoring a new SKILL.md and forgetting the description field; stripping frontmatter keys with a linter/formatter; copying a skill template with placeholder removed; renaming keys (e.g. 'summary') instead of 'description'.
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- Invalid hook point '{hook_point}'. Valid hook points are: {'
- Hook registered for hook point '{hook_point}' is callable bu
- Hook registered for hook point '{hook_point}' must have a ca
- {type(chat_generator).__name__} does not accept tools parame
- 'dimension' must be a positive integer.
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/9cfdffa59906dd0c.
Report an issue: GitHub.