{"record":{"id":"7e1219902b7ef561","repo":"langchain-ai/deepagents","slug":"expected-notificationdecision-got-type-decision","errorCode":null,"errorMessage":"Expected NotificationDecision, got {type(decision).__name__}","messagePattern":"Expected NotificationDecision, got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"warning","filePath":"libs/code/deepagents_code/hooks/client_lifecycle.py","lineNumber":360,"sourceCode":"            ClientHookStopError: If a handler stops lifecycle processing.\n            TypeError: If the runtime returns a mismatched decision type.\n        \"\"\"\n        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.","sourceCodeStart":342,"sourceCodeEnd":378,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/hooks/client_lifecycle.py#L342-L378","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nreturn NotificationDecision(continue_processing=False)\n\n// after\nreturn NotificationDecision(continue_processing=False, stop_reason=\"user muted channel notifications\")","handlingStrategy":"try-catch","validationCode":"from deepagents_code.hooks import NotificationDecision\n\nresult = my_notification_hook(context, event)\nif not isinstance(result, NotificationDecision):\n    result = NotificationDecision.model_validate(result)\n","typeGuard":"def is_notification_decision(value: object) -> bool:\n    return isinstance(value, NotificationDecision)\n","tryCatchPattern":"try:\n    decision = await service.notification(context, notification)\nexcept ClientHookStopError as exc:\n    logger.info(\"notification hook stopped processing: %s\", exc)\n    return\n","preventionTips":["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"],"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"}