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
- Add a stop_reason to the NotificationDecision so the raised ClientHookStopError is self-explanatory
- Catch ClientHookStopError in the caller if stopping is an expected outcome of your notification hooks
- If you did not expect a stop, inspect registered notification hooks for one returning continue_processing=False
- 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
- Always provide stop_reason when returning continue_processing=False
- Catch ClientHookStopError around notify() since hooks may legitimately stop processing
- Review notification hooks when this default message appears unexpectedly
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
- Expected NotificationDecision, got {type(decision).__name__}
- 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/3e56849e8c3bc709.
Report an issue: GitHub.