{"record":{"id":"cef3027cc2398b94","repo":"deepset-ai/haystack","slug":"hook-registered-for-hook-point-hook-point-is-c","errorCode":null,"errorMessage":"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.","messagePattern":"Hook registered for hook point '(.+?)' is callable but is not a Hook object\\. If it is a function, wrap it with the @hook decorator\\.","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"haystack/components/agents/agent.py","lineNumber":129,"sourceCode":"\n\ndef _validate_hooks(hooks: dict[HookPoint, list[Hook]]) -> None:\n    \"\"\"\n    Validate a hooks mapping: known hook points, real Hook objects, and hook-point restrictions.\n\n    :param hooks: Mapping of hook point to the hooks registered under it.\n    :raises ValueError: If a hook point is unknown, or a hook is registered under a hook point it does not support.\n    :raises TypeError: If a registered hook has no callable `run(state)`.\n    \"\"\"\n    for hook_point, hook_list in hooks.items():\n        if hook_point not in VALID_HOOK_POINTS:\n            raise ValueError(\n                f\"Invalid hook point '{hook_point}'. Valid hook points are: {', '.join(VALID_HOOK_POINTS)}.\"\n            )\n        for h in hook_list:\n            if not callable(getattr(h, \"run\", None)):\n                if callable(h):\n                    raise TypeError(\n                        f\"Hook registered for hook point '{hook_point}' is callable but is not a Hook object. \"\n                        \"If it is a function, wrap it with the @hook decorator.\"\n                    )\n                raise TypeError(\n                    f\"Hook registered for hook point '{hook_point}' must have a callable 'run(state)', \"\n                    f\"got an object of type '{type(h).__name__}'.\"\n                )\n            # A hook may declare `allowed_hook_points` to restrict where it can run (e.g. ConfirmationHook only\n            # makes sense at \"before_tool\"). Hooks without it can be registered under any hook point.\n            allowed_points = getattr(h, \"allowed_hook_points\", None)\n            if allowed_points is not None and hook_point not in allowed_points:\n                raise ValueError(\n                    f\"Hook of type '{type(h).__name__}' is registered under hook point '{hook_point}' but only \"\n                    f\"supports: {', '.join(allowed_points)}.\"\n                )\n\n\ndef _consume_continue_run(state: State) -> bool:","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/components/agents/agent.py#L111-L147","documentation":"After successful YAML parsing in _parse_frontmatter, the loaded value must be a mapping (dict). This ValueError is raised when the frontmatter parses to a non-dict (e.g. a YAML list, scalar string, or null). Skill frontmatter is required to be key/value metadata, so any other YAML root type is rejected.","triggerScenarios":"Frontmatter block that is a YAML sequence (lines starting with '-'), a bare scalar (just a word or number), or content that collapses to None/non-dict, e.g. malformed delimiters causing the wrong region to be parsed.","commonSituations":"Deleting all keys but leaving '---' lines; accidental indentation turning keys into a nested list; a stray '-' bullet inside frontmatter; delimiters mis-detected so body text is parsed as frontmatter.","solutions":["Rewrite the frontmatter between the '---' lines as key: value pairs (name, description).","Check that the first '---' is on line 1 and a second '---' closes the block, so only the mapping is parsed.","Test the block with yaml.safe_load and confirm it returns a dict."],"exampleFix":"# before\n---\n- name: my-skill\n- description: does stuff\n---\n# after\n---\nname: my-skill\ndescription: does stuff\n---","handlingStrategy":"validation","validationCode":"import yaml\nfrom pathlib import Path\n\ndef frontmatter_is_mapping(skill_file: Path) -> bool:\n    lines = skill_file.read_text(encoding=\"utf-8\").splitlines()\n    closing = lines.index(\"---\", 1)\n    block = \"\\n\".join(lines[1:closing])\n    loaded = yaml.safe_load(block) or {}\n    return isinstance(loaded, dict) and \"description\" in loaded","typeGuard":"def is_frontmatter_mapping(loaded) -> bool:\n    return isinstance(loaded, dict)","tryCatchPattern":"try:\n    store.load_skill(name)\nexcept ValueError as e:\n    if \"must be a YAML mapping\" in str(e):\n        logger.error(\"Frontmatter of %s must be key: value pairs\", name)\n    raise","preventionTips":["Author frontmatter strictly as key: value pairs, never lists or bare scalars","Verify delimiters: first '---' on line 1, second '---' closes the block","Add a CI check asserting yaml.safe_load(frontmatter) is a dict with required keys"],"tags":["yaml","skill-store","frontmatter","type-error"],"backgroundTag":"invalid-yaml-frontmatter","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}