langchain-ai/deepagents · warning · TypeError

Expected NotificationDecision, got {type(decision).__name__}

Error message

Expected NotificationDecision, got {type(decision).__name__}

What it means

ClientHookService.notification validates that the notification hook returned a NotificationDecision, then checks continue_processing: if the hook decided to stop, it raises ClientHookStopError carrying decision.stop_reason, defaulting to 'Notification stopped by hook'. This lets a notification hook halt further processing of the notification event.

Source

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

            ClientHookStopError: If a handler stops lifecycle processing.
            TypeError: If the runtime returns a mismatched decision type.
        """
        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.

View on GitHub (pinned to a1af029e6e)

Solutions

  1. If the stop is intentional, catch ClientHookStopError around notify() and use decision.stop_reason to distinguish it
  2. Set a descriptive stop_reason on the NotificationDecision so the error message is actionable
  3. If the stop is unintended, construct the decision with continue_processing=True or fix the config driving it
  4. Verify the hook is not accidentally sharing a decision object mutated elsewhere

Example fix

// before
return NotificationDecision(continue_processing=False)

// after
return NotificationDecision(continue_processing=False, stop_reason="user muted channel notifications")
Defensive patterns

Strategy: try-catch

Validate before calling

from deepagents_code.hooks import NotificationDecision

result = my_notification_hook(context, event)
if not isinstance(result, NotificationDecision):
    result = NotificationDecision.model_validate(result)

Type guard

def is_notification_decision(value: object) -> bool:
    return isinstance(value, NotificationDecision)

Try / catch

try:
    decision = await service.notification(context, notification)
except ClientHookStopError as exc:
    logger.info("notification hook stopped processing: %s", exc)
    return

Prevention

When it happens

Trigger: A registered NOTIFICATION hook returns a NotificationDecision with continue_processing=False and no stop_reason, and the service propagates the stop as ClientHookStopError with the default message.

Common situations: A notification-filtering hook intentionally suppressing further notifications (expected, not a bug); a hook that builds the decision from a config file where 'continue_processing' was accidentally false; forgetting to set stop_reason so the generic message appears.

Related errors


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