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
- If the stop is intentional, catch ClientHookStopError around notify() and use decision.stop_reason to distinguish it
- Set a descriptive stop_reason on the NotificationDecision so the error message is actionable
- If the stop is unintended, construct the decision with continue_processing=True or fix the config driving it
- 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
- Treat ClientHookStopError as a normal control-flow signal for stop decisions
- Always set stop_reason on stop decisions so messages are actionable
- Verify config-driven continue_processing flags cannot be silently false
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
- Notification stopped by hook
- Permission interrupted by hook
- Compact session start stopped by hook
- Session start stopped by hook
- Offload server returned an invalid operation response.
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/7e1219902b7ef561.
Report an issue: GitHub.