{"record":{"id":"df99177728f561e0","repo":"deepset-ai/haystack","slug":"hook-function-func-name-must-take-a-single","errorCode":null,"errorMessage":"Hook function '{func.__name__}' must take a single parameter annotated with `State` (e.g. `def my_hook(state: State) -> None`).","messagePattern":"Hook function '(.+?)' must take a single parameter annotated with `State` \\(e\\.g\\. `def my_hook\\(state: State\\) -> None`\\)\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"haystack/hooks/from_function.py","lineNumber":63,"sourceCode":"            `run` raises a `RuntimeError`. When only `function` is set, `run_async` calls `function`.\n        :raises ValueError: If neither is set, if `function` is a coroutine function, if `async_function` is not, or\n            if a provided function does not declare a `State`-typed parameter.\n        \"\"\"\n        if function is None and async_function is None:\n            raise ValueError(\"A FunctionHook requires at least one of `function` or `async_function` to be set.\")\n        if function is not None and inspect.iscoroutinefunction(function):\n            raise ValueError(\n                f\"`function` must be a synchronous function. '{function.__name__}' is a coroutine function. \"\n                \"Pass it as `async_function` instead.\"\n            )\n        if async_function is not None and not inspect.iscoroutinefunction(async_function):\n            raise ValueError(\n                f\"`async_function` must be a coroutine function defined with `async def`. \"\n                f\"Got '{getattr(async_function, '__name__', repr(async_function))}'.\"\n            )\n        for func in (function, async_function):\n            if func is not None and not _takes_single_state_argument(func):\n                raise ValueError(\n                    f\"Hook function '{func.__name__}' must take a single parameter annotated with `State` \"\n                    \"(e.g. `def my_hook(state: State) -> None`).\"\n                )\n        self.function = function\n        self.async_function = async_function\n\n    def run(self, state: State) -> None:\n        \"\"\"\n        Run the synchronous function against the live `State`.\n\n        :param state: The Agent's live `State`, mutated in place by the wrapped function.\n        :raises RuntimeError: If the hook only has an `async_function`; use the Agent's async run methods instead.\n        \"\"\"\n        if self.function is None:\n            raise RuntimeError(\n                \"This FunctionHook only has an `async_function` and cannot run in a synchronous Agent run. \"\n                \"Use the Agent's async run methods, or provide a synchronous `function`.\"\n            )","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/hooks/from_function.py#L45-L81","documentation":"FunctionHook hook functions must declare exactly one parameter annotated with `State`, because the Agent injects its live State as the sole argument. Any sync or async function supplied whose signature does not match this contract raises ValueError at construction time.","triggerScenarios":"Passing to `function`/`async_function` a callable with zero parameters, multiple parameters, or a parameter not annotated as `State`, detected by _takes_single_state_argument.","commonSituations":"Writing hooks like `def my_hook(state)` without the annotation; reusing an existing callback with extra kwargs; typos in the State import so the annotation is a different type.","solutions":["Change the hook signature to exactly `def my_hook(state: State) -> None` (or `async def`)","Ensure `State` is the real class imported from haystack (not a string alias or wrong type)","If extra context is needed, close over it instead of adding parameters"],"exampleFix":"// before\ndef my_hook(state, extra_ctx): ...\nhook = FunctionHook(function=my_hook)  # ValueError\n// after\nfrom haystack import State\ndef my_hook(state: State) -> None: ...\nhook = FunctionHook(function=my_hook)","handlingStrategy":"validation","validationCode":"import inspect\nfrom haystack import State\nsig = inspect.signature(fn)\nparams = list(sig.parameters.values())\nassert len(params) == 1 and params[0].annotation is State, \"hook must take one State-annotated param\"","typeGuard":"import inspect\nfrom typing import get_type_hints\nfrom haystack import State\ndef is_state_hook(fn) -> bool:\n    params = list(inspect.signature(fn).parameters.values())\n    if len(params) != 1:\n        return False\n    return get_type_hints(fn).get(params[0].name) is State","tryCatchPattern":"try:\n    hook = FunctionHook(function=fn)\nexcept ValueError as e:\n    raise TypeError(f\"hook signature invalid: {e}\") from e","preventionTips":["Use the canonical signature: def hook(state: State) -> None","Import State from haystack so the annotation matches exactly","Close over extra context instead of adding parameters","Type-check hooks with mypy"],"tags":["python","validation","hooks","signature"],"backgroundTag":"invalid-callback-signature","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}