{"record":{"id":"ef3cb8395d8d21cb","repo":"langchain-ai/deepagents","slug":"expected-permissionrequestdecision-got-type-deci","errorCode":null,"errorMessage":"Expected PermissionRequestDecision, got {type(decision).__name__}","messagePattern":"Expected PermissionRequestDecision, got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/hooks/client_lifecycle.py","lineNumber":285,"sourceCode":"\n        Returns:\n            Aggregated permission decision.\n\n        Raises:\n            TypeError: If the runtime returns a mismatched decision type.\n        \"\"\"\n        if not self.has_handlers(HookEvent.PERMISSION_REQUEST):\n            return PermissionRequestDecision(\n                event=HookEvent.PERMISSION_REQUEST,\n                permission=PermissionEffect(behavior=\"none\"),\n            )\n        decision = await self._invoke(\n            context,\n            PermissionRequestEvent(event=HookEvent.PERMISSION_REQUEST, call=call),\n        )\n        if not isinstance(decision, PermissionRequestDecision):\n            msg = f\"Expected PermissionRequestDecision, got {type(decision).__name__}\"\n            raise TypeError(msg)\n        return decision\n\n    async def resolve_permission(\n        self,\n        context: ClientHookContext,\n        call: ToolCallData,\n    ) -> PermissionHookOutcome:\n        \"\"\"Resolve a permission hook and present user-facing attribution once.\n\n        The returned HITL decision carries the raw hook reason (or stop reason)\n        for model-visible resume payloads. Attribution text is emitted only\n        through the shared presenter.\n\n        Args:\n            context: Current client session context.\n            call: Tool action awaiting approval.\n\n        Returns:","sourceCodeStart":267,"sourceCodeEnd":303,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/hooks/client_lifecycle.py#L267-L303","documentation":"ClientHookService.permission_request validates that the hook handler returned a PermissionRequestDecision before the permission flow resolves a tool call. If the handler returns any other object (dict, None, wrong class), the service raises TypeError. This keeps the permission pipeline typed: downstream code expects fields like allow/deny on the decision.","triggerScenarios":"Calling permission_request() (or resolve_permission) when a registered PERMISSION_REQUEST hook returns {'allow': True}, None, or a decision class from another event instead of PermissionRequestDecision.","commonSituations":"Generic permission callbacks shared across tools returning ad-hoc dicts; older hook code written before the typed decision API; building the decision from persisted JSON without re-validating to the concrete class.","solutions":["Return PermissionRequestDecision.allow()/deny() (or model_validate the raw dict) from the PERMISSION_REQUEST hook","Re-validate any deserialized decisions with PermissionRequestDecision before returning","Ensure the callback is registered for permission_request and not reused from another hook event","If a custom wrapper strips types, change it to pass through the original decision object"],"exampleFix":"// before\ndef check_perm(context, event):\n    return {\"allow\": True}\n\n// after\nfrom deepagents_code.hooks import PermissionRequestDecision\n\ndef check_perm(context, event):\n    return PermissionRequestDecision.allow()","handlingStrategy":"type-guard","validationCode":"from deepagents_code.hooks import PermissionRequestDecision\n\nresult = my_permission_hook(context, event)\nif not isinstance(result, PermissionRequestDecision):\n    result = PermissionRequestDecision.model_validate(result)\n","typeGuard":"def is_permission_decision(value: object) -> bool:\n    return isinstance(value, PermissionRequestDecision)\n","tryCatchPattern":"try:\n    decision = await service.permission_request(context, call)\nexcept TypeError as exc:\n    logger.warning(\"permission hook returned invalid decision: %s\", exc)\n    decision = PermissionRequestDecision.deny(reason=\"invalid hook response\")\n","preventionTips":["Return PermissionRequestDecision.allow()/deny() helpers rather than hand-building dicts","Annotate permission hook callbacks with the concrete return type","Fail closed: validate hook output before trusting allow/deny fields"],"tags":["hooks","permissions","type-validation","typeerror"],"backgroundTag":"hook-decision-type-mismatch","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}