{"record":{"id":"32666cd41f22eca8","repo":"deepset-ai/haystack","slug":"hook-registered-for-hook-point-hook-point-must","errorCode":null,"errorMessage":"Hook registered for hook point '{hook_point}' must have a callable 'run(state)', got an object of type '{type(h).__name__}'.","messagePattern":"Hook registered for hook point '(.+?)' must have a callable 'run\\(state\\)', got an object of type '(.+?)'\\.","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"haystack/components/agents/agent.py","lineNumber":133,"sourceCode":"    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:\n    \"\"\"Return the `continue_run` control flag and reset it so it does not carry over to the next exit attempt.\"\"\"\n    should_continue = state.data[\"continue_run\"]\n    state.set(\"continue_run\", False)\n    return should_continue","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/components/agents/agent.py#L115-L151","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nstore = FileSystemSkillStore(skills_dir=\"./skills\")\n// after\nfrom pathlib import Path\nskills_dir = Path(\"./skills\").resolve()\nif not skills_dir.is_dir():\n    skills_dir.mkdir(parents=True)\nstore = FileSystemSkillStore(skills_dir=skills_dir)","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef ensure_skills_dir(skills_dir) -> Path:\n    path = Path(skills_dir).resolve()\n    if not path.is_dir():\n        raise NotADirectoryError(f\"skills_dir does not exist or is not a directory: {path}\")\n    return path\n\nstore = FileSystemSkillStore(skills_dir=ensure_skills_dir(\"./skills\"))","typeGuard":null,"tryCatchPattern":"try:\n    skills = store.list_skills()\nexcept ValueError as e:\n    if \"does not exist or is not a directory\" in str(e):\n        Path(skills_dir).mkdir(parents=True, exist_ok=True)\n        skills = store.list_skills()\n    else:\n        raise","preventionTips":["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"],"tags":["filesystem","skill-store","configuration","path"],"backgroundTag":"path-not-found","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}