{"record":{"id":"3e56849e8c3bc709","repo":"langchain-ai/deepagents","slug":"notification-stopped-by-hook","errorCode":null,"errorMessage":"Notification stopped by hook","messagePattern":"Notification stopped by hook","errorType":"exception","errorClass":"ClientHookStopError","httpStatus":null,"severity":"warning","filePath":"libs/code/deepagents_code/hooks/client_lifecycle.py","lineNumber":363,"sourceCode":"        if not self.has_handlers(HookEvent.NOTIFICATION):\n            return NotificationDecision(event=HookEvent.NOTIFICATION)\n        decision = await self._invoke(\n            context,\n            NotificationEvent(\n                event=HookEvent.NOTIFICATION,\n                notification=DcodeNotification(\n                    type=kind,\n                    message=message,\n                    title=title,\n                ),\n            ),\n        )\n        if not isinstance(decision, NotificationDecision):\n            msg = f\"Expected NotificationDecision, got {type(decision).__name__}\"\n            raise TypeError(msg)\n        if not decision.continue_processing:\n            reason = decision.stop_reason or \"Notification stopped by hook\"\n            raise ClientHookStopError(reason)\n        return decision\n\n    def take_session_context(self, thread_id: str) -> tuple[str, ...]:\n        \"\"\"Consume context accumulated for the thread's next model turn.\n\n        Args:\n            thread_id: Thread whose pending context should be consumed.\n\n        Returns:\n            Ordered context strings, removed from the service.\n        \"\"\"\n        return tuple(self._pending_context.pop(thread_id, ()))\n\n    def has_handlers(self, event: HookEvent) -> bool:\n        \"\"\"Return whether the runtime has handlers for an event.\n\n        Args:\n            event: Lifecycle event to inspect.","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/hooks/client_lifecycle.py#L345-L381","documentation":"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.","triggerScenarios":"Calling notify() while a registered NOTIFICATION hook returns NotificationDecision(continue_processing=False) with stop_reason=None (or empty).","commonSituations":"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.","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"],"exampleFix":"// before\nreturn NotificationDecision(continue_processing=False)\n\n// after\nreturn NotificationDecision(continue_processing=False, stop_reason=\"duplicate notification suppressed\")","handlingStrategy":"try-catch","validationCode":"from deepagents_code.hooks import NotificationDecision\n\ndecision = NotificationDecision.model_validate(raw)\nif not decision.continue_processing and not decision.stop_reason:\n    decision.stop_reason = \"suppressed by hook\"\n","typeGuard":"def is_stopping_notification(value: object) -> bool:\n    return isinstance(value, NotificationDecision) and not value.continue_processing\n","tryCatchPattern":"try:\n    await service.notification(context, notification)\nexcept ClientHookStopError as exc:\n    if str(exc) == \"Notification stopped by hook\":\n        logger.debug(\"a notification hook stopped without a reason\")\n    return\n","preventionTips":["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"],"tags":["hooks","notifications","stop-hook"],"backgroundTag":"hook-stop-requested","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}