langchain-ai/deepagents · warning · ClientHookStopError

Notification stopped by hook

Error message

Notification stopped by hook

What it means

This is the default reason attached to ClientHookStopError when a notification hook returned a NotificationDecision with continue_processing=False but did not supply stop_reason. The library raises ClientHookStopError so callers know a hook deliberately stopped notification processing, using this fallback text because no custom reason was given.

Source

Thrown at libs/code/deepagents_code/hooks/client_lifecycle.py:363

        if not self.has_handlers(HookEvent.NOTIFICATION):
            return NotificationDecision(event=HookEvent.NOTIFICATION)
        decision = await self._invoke(
            context,
            NotificationEvent(
                event=HookEvent.NOTIFICATION,
                notification=DcodeNotification(
                    type=kind,
                    message=message,
                    title=title,
                ),
            ),
        )
        if not isinstance(decision, NotificationDecision):
            msg = f"Expected NotificationDecision, got {type(decision).__name__}"
            raise TypeError(msg)
        if not decision.continue_processing:
            reason = decision.stop_reason or "Notification stopped by hook"
            raise ClientHookStopError(reason)
        return decision

    def take_session_context(self, thread_id: str) -> tuple[str, ...]:
        """Consume context accumulated for the thread's next model turn.

        Args:
            thread_id: Thread whose pending context should be consumed.

        Returns:
            Ordered context strings, removed from the service.
        """
        return tuple(self._pending_context.pop(thread_id, ()))

    def has_handlers(self, event: HookEvent) -> bool:
        """Return whether the runtime has handlers for an event.

        Args:
            event: Lifecycle event to inspect.

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Add a stop_reason to the NotificationDecision so the raised ClientHookStopError is self-explanatory
  2. Catch ClientHookStopError in the caller if stopping is an expected outcome of your notification hooks
  3. If you did not expect a stop, inspect registered notification hooks for one returning continue_processing=False
  4. Log the hook name when constructing stop decisions to make this message traceable

Example fix

// before
return NotificationDecision(continue_processing=False)

// after
return NotificationDecision(continue_processing=False, stop_reason="duplicate notification suppressed")
Defensive patterns

Strategy: try-catch

Validate before calling

from deepagents_code.hooks import NotificationDecision

decision = NotificationDecision.model_validate(raw)
if not decision.continue_processing and not decision.stop_reason:
    decision.stop_reason = "suppressed by hook"

Type guard

def is_stopping_notification(value: object) -> bool:
    return isinstance(value, NotificationDecision) and not value.continue_processing

Try / catch

try:
    await service.notification(context, notification)
except ClientHookStopError as exc:
    if str(exc) == "Notification stopped by hook":
        logger.debug("a notification hook stopped without a reason")
    return

Prevention

When it happens

Trigger: Calling notify() while a registered NOTIFICATION hook returns NotificationDecision(continue_processing=False) with stop_reason=None (or empty).

Common situations: Minimal hook implementations that only flip continue_processing off; decisions deserialized from JSON missing the stop_reason field; tests exercising the stop path without setting a reason.

Related errors


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