{"record":{"id":"16382adba57aa4f4","repo":"agentscope-ai/agentscope","slug":"kind-value-replace-title-resource","errorCode":null,"errorMessage":"{kind.value.replace('_', ' ').title()} '{resource_id}' is read-only for this viewer.","messagePattern":"(.+?) '(.+?)' is read-only for this viewer\\.","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"src/agentscope/app/_service/_access.py","lineNumber":505,"sourceCode":"        \"\"\"\n        own = await self._get_owned(kind, viewer_id, resource_id)\n        if own is not None:\n            return viewer_id, own\n\n        for ref in await self._list_refs(viewer_id, kind):\n            if ref.resource_id != resource_id:\n                continue\n            record = await self._get_owned(\n                kind,\n                ref.owner_id,\n                ref.resource_id,\n            )\n            if record is None:\n                continue\n            if isinstance(record, AgentRecord) and record.source == \"team\":\n                continue\n            if ref.permission != ResourcePermission.EDIT:\n                raise HTTPException(\n                    status_code=status.HTTP_403_FORBIDDEN,\n                    detail=(\n                        f\"{kind.value.replace('_', ' ').title()} \"\n                        f\"'{resource_id}' is read-only for this viewer.\"\n                    ),\n                )\n            return ref.owner_id, record\n        raise self._not_found(kind, resource_id)\n\n    # ------------------------------------------------------------------\n    # Internals\n    # ------------------------------------------------------------------\n\n    async def _get_owned(\n        self,\n        kind: ResourceKind,\n        owner_id: str,\n        resource_id: str,","sourceCodeStart":487,"sourceCodeEnd":523,"githubUrl":"https://github.com/agentscope-ai/agentscope/blob/e90f1c7592896cc95f6e5ee506194f533378247d/src/agentscope/app/_service/_access.py#L487-L523","documentation":"Thrown by the access-control service's resolve_for_edit when the viewer has at most read permission (ResourcePermission != EDIT) for the target resource. AgentScope's app layer distinguishes read-only viewers from editors; any mutation attempt on a resource the viewer can only see is rejected with HTTP 403. Team-sourced agent records are skipped as candidates, so only explicitly granted edit permission counts.","triggerScenarios":"Calling update_agent, delete_agent, update_credential, or delete_credential for a resource_id where the current user's permission reference is VIEW/READ. Commonly occurs for agents shared with a user as viewers or agents discovered via a team.","commonSituations":"A shared workspace where an admin granted 'view' instead of 'edit'; scripts written with an owner account but run with a collaborator token; UI hiding the edit/disable state so users click edit on read-only items; freshly issued API tokens with default viewer scope.","solutions":["Have an owner/admin grant EDIT permission on that resource to the current viewer","Perform the mutation with a user/token that already has edit rights","In the UI, disable edit/delete actions when the resolved permission is not EDIT","If you own the resource, check that you are authenticating as the owner account rather than a team viewer identity"],"exampleFix":"// before\nawait client.update_agent(agent_id=\"agent-123\", ...)  # 403\n# after: grant edit first (as owner/admin)\nawait admin_client.grant_permission(resource=\"agent\", resource_id=\"agent-123\", user_id=viewer_id, permission=\"EDIT\")\nawait client.update_agent(agent_id=\"agent-123\", ...)","handlingStrategy":"validation","validationCode":"perm = await access_client.get_permission(kind=\"agent\", resource_id=agent_id)\nif perm != \"EDIT\":\n    raise PermissionError(f\"Viewer cannot modify {agent_id}; ask owner for EDIT\")\nawait client.update_agent(agent_id=agent_id, ...)","typeGuard":"def can_edit(perm: str) -> bool:\n    return perm == \"EDIT\"  # ResourcePermission.EDIT value","tryCatchPattern":"try:\n    await client.update_agent(...)\nexcept HTTPStatusError as e:\n    if e.response.status_code == 403:\n        prompt_user_for_edit_access(resource_id)\n    else:\n        raise","preventionTips":["Fetch and cache the resolved permission before enabling edit/delete UI actions","Run maintenance scripts with a token that has EDIT on all target resources"],"tags":["authorization","http-403","permissions","access-control"],"backgroundTag":"permission-denied-403","analyzedSha":"e90f1c7592896cc95f6e5ee506194f533378247d","analyzedAt":"2026-08-28T18:24:12.087Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}