langchain-ai/deepagents · error · ExtensionError
Shutdown hook is not callable
Error message
Shutdown hook is not callable
What it means
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.
Source
Thrown at libs/code/deepagents_code/extensions/api.py:187
f"Backend route {prefix!r} got {type(backend).__name__}, "
"which is not a BackendProtocol"
)
raise ExtensionError(msg)
self._registry.add_backend_route(prefix, backend, self._source)
def on_shutdown(self, hook: Callable[[], Any]) -> None:
"""Register a deterministic session teardown callback.
Args:
hook: Sync or async zero-argument callback.
Raises:
ExtensionError: If `hook` is not callable.
"""
self._ensure_active()
if not callable(hook):
msg = "Shutdown hook is not callable"
raise ExtensionError(msg)
self._registry.add_shutdown_hook(hook, self._source)
View on GitHub (pinned to a1af029e6e)
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
Example fix
// before ext.on_shutdown(connection.close()) // after ext.on_shutdown(lambda: connection.close())
Defensive patterns
Strategy: type-guard
Validate before calling
if not callable(hook):
raise TypeError("shutdown hook must be callable") Type guard
from collections.abc import Callable
def is_shutdown_hook(hook: object) -> bool:
return callable(hook) Try / catch
try:
ext.on_shutdown(hook)
except ExtensionError as exc:
logger.error("shutdown hook rejected: %s", exc) Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Wrong argument type", "must be a string", "expected Array or Prism::Scope": TypeError and ArgumentError when a library receives a value of the wrong type — this error's family across 28 libraries.
Related errors
- Registered middleware must be an AgentMiddleware, got {kind}
- Backend route {prefix!r} got {type(backend).__name__}, which
- '{class_path}' is not a BaseChatModel subclass (got {type(cl
- Extension registration is closed for this session
- Could not construct middleware {middleware!r}: {exc}
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/2686240b5c4800e1.
Report an issue: GitHub.