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 the

View on GitHub (pinned to e318778c9b)

Solutions

  1. Add a non-empty 'description: ...' line to the skill file's frontmatter.
  2. 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.
  3. 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

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


AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30). Data as JSON: /api/errors/9cfdffa59906dd0c. Report an issue: GitHub.