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

  1. Pass the callable itself, not the result of calling it: on_shutdown(cleanup) not on_shutdown(cleanup())
  2. Check that the hook variable is not None (e.g. an optional callback that defaulted to None)
  3. 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

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


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/2686240b5c4800e1. Report an issue: GitHub.