{"record":{"id":"50990ff7249062b0","repo":"langflow-ai/langflow","slug":"system-roles-cannot-be-deleted","errorCode":null,"errorMessage":"System roles cannot be deleted","messagePattern":"System roles cannot be deleted","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"src/backend/base/langflow/api/v1/authz_roles.py","lineNumber":272,"sourceCode":"\n@router.delete(\"/{role_id}\", status_code=status.HTTP_204_NO_CONTENT)\nasync def delete_role(\n    role_id: UUID,\n    current_user: CurrentActiveUser,\n    session: DbSession,\n) -> None:\n    \"\"\"Delete a custom role.\n\n    System roles cannot be deleted; roles with active assignments return 409\n    (delete the assignments first).\n    \"\"\"\n    _require_superuser(current_user)\n\n    role = await session.get(AuthzRole, role_id)\n    if role is None:\n        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=\"Role not found\")\n    if role.is_system:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=\"System roles cannot be deleted\",\n        )\n\n    assigned = (\n        await session.exec(select(AuthzRoleAssignment).where(AuthzRoleAssignment.role_id == role_id).limit(1))\n    ).first()\n    if assigned is not None:\n        raise HTTPException(\n            status_code=status.HTTP_409_CONFLICT,\n            detail=\"Role still has active assignments — revoke them before deleting\",\n        )\n\n    role_name = role.name\n    await session.delete(role)\n    await session.commit()\n    await safe_invalidate_role(get_authorization_service(), role_id, op=\"role:delete\")\n    await audit_decision(","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/v1/authz_roles.py#L254-L290","documentation":"Raised by DELETE /api/v1/authz/roles/{role_id} when the target role has is_system=True. The three built-in roles (viewer / developer / admin) are seeded with is_system=True and are protected from deletion so the default role catalog stays stable for authorization plugins. Returns 400, not 404.","triggerScenarios":"DELETE /authz/roles/{id} where the id belongs to a seeded system role (viewer, developer, or admin); scripts that iterate all roles and delete each one.","commonSituations":"Cleanup scripts that assume every role is deletable; attempting to remove the default roles before registering an authorization plugin that still references them; confusing the system 'admin' role with a custom role coincidentally named 'admin'.","solutions":["Skip roles with is_system=true when bulk-deleting (filter them out of your iteration)","If you want a role with different semantics, create a new custom role instead of deleting a system one","Check the role's is_system flag via GET /authz/roles before offering a delete button in the UI"],"exampleFix":"// before\nfor (const role of await listRoles()) {\n  await deleteRole(role.id); // 400 on system roles\n}\n\n// after\nfor (const role of await listRoles()) {\n  if (!role.is_system) await deleteRole(role.id);\n}","handlingStrategy":"type-guard","validationCode":"const roles = await listRoles();\nconst deletable = roles.filter(r => !r.is_system);","typeGuard":"interface Role { id: string; name: string; is_system: boolean }\nconst isDeletableRole = (r: Role): boolean => !r.is_system;","tryCatchPattern":null,"preventionTips":["Filter is_system roles out of any bulk delete iteration","Disable the delete affordance in UI when role.is_system is true"],"tags":["authz","api","roles","system-roles","delete"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}