{"record":{"id":"2686240b5c4800e1","repo":"langchain-ai/deepagents","slug":"shutdown-hook-is-not-callable","errorCode":null,"errorMessage":"Shutdown hook is not callable","messagePattern":"Shutdown hook is not callable","errorType":"validation","errorClass":"ExtensionError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/extensions/api.py","lineNumber":187,"sourceCode":"                f\"Backend route {prefix!r} got {type(backend).__name__}, \"\n                \"which is not a BackendProtocol\"\n            )\n            raise ExtensionError(msg)\n        self._registry.add_backend_route(prefix, backend, self._source)\n\n    def on_shutdown(self, hook: Callable[[], Any]) -> None:\n        \"\"\"Register a deterministic session teardown callback.\n\n        Args:\n            hook: Sync or async zero-argument callback.\n\n        Raises:\n            ExtensionError: If `hook` is not callable.\n        \"\"\"\n        self._ensure_active()\n        if not callable(hook):\n            msg = \"Shutdown hook is not callable\"\n            raise ExtensionError(msg)\n        self._registry.add_shutdown_hook(hook, self._source)\n","sourceCodeStart":169,"sourceCodeEnd":189,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/extensions/api.py#L169-L189","documentation":"Raised by ExtensionApi.on_shutdown when the registered teardown hook is not callable. Shutdown hooks are invoked deterministically at session teardown, so the library requires an actual callable before accepting it.","triggerScenarios":"Calling `ext.on_shutdown(obj)` where obj is None, a string, a lambda result that was invoked (None), or any non-callable value; passing the result of a function instead of the function itself.","commonSituations":"Writing on_shutdown(cleanup()) instead of on_shutdown(cleanup); typo'd attribute that resolved to None; passing a constant or config value instead of the callback.","solutions":["Pass the callable itself, not the result of calling it: on_shutdown(cleanup) not on_shutdown(cleanup())","Check that the hook variable is not None (e.g. an optional callback that defaulted to None)","Wrap non-callable teardown logic in a function before registering it"],"exampleFix":"// before\next.on_shutdown(connection.close())\n\n// after\next.on_shutdown(lambda: connection.close())","handlingStrategy":"type-guard","validationCode":"if not callable(hook):\n    raise TypeError(\"shutdown hook must be callable\")","typeGuard":"from collections.abc import Callable\n\ndef is_shutdown_hook(hook: object) -> bool:\n    return callable(hook)","tryCatchPattern":"try:\n    ext.on_shutdown(hook)\nexcept ExtensionError as exc:\n    logger.error(\"shutdown hook rejected: %s\", exc)","preventionTips":["Register the function itself, never the result of calling it","Type-annotate hook parameters as Callable[[], Any] to catch mistakes early","Beware optional hooks: check for None before registering"],"tags":["extensions","shutdown-hook","type-check"],"backgroundTag":"wrong-argument-type","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}