{"record":{"id":"6c2d84b1394e8eef","repo":"langflow-ai/langflow","slug":"role-still-has-active-assignments-revoke-them-be","errorCode":null,"errorMessage":"Role still has active assignments — revoke them before deleting","messagePattern":"Role still has active assignments — revoke them before deleting","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"error","filePath":"src/backend/base/langflow/api/v1/authz_roles.py","lineNumber":281,"sourceCode":"    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(\n        user_id=current_user.id,\n        action=\"role:delete\",\n        obj=f\"role:{role_id}\",\n        result=\"allow\",\n        details={\"role_name\": role_name},\n    )\n    logger.info(\"Deleted role id=%s\", role_id)\n","sourceCodeStart":263,"sourceCodeEnd":298,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/v1/authz_roles.py#L263-L298","documentation":"Raised by DELETE /api/v1/authz/roles/{role_id} when a row exists in authz_role_assignment referencing the role (the endpoint probes with a LIMIT 1 query before deleting). The DB would reject the delete via foreign key, so the API returns a proactive 409 telling you to revoke the assignments first.","triggerScenarios":"DELETE /authz/roles/{id} while any user or team still has that role assigned via POST /authz/assignments (authz_role_assignment rows with role_id = id).","commonSituations":"Decommissioning a role that is still granted to a team; test fixtures that assign roles and forget to revoke them; ordering bug in teardown scripts that deletes roles before assignments.","solutions":["List and revoke the role's assignments first (query the assignments endpoint and DELETE each one for this role_id), then retry the role delete","Reassign affected users to a replacement role before removing the old one","In teardown scripts, delete assignments before roles"],"exampleFix":"// before\nawait deleteRole(roleId); // 409: assignments exist\n\n// after\nconst assignments = await listAssignments({ role_id: roleId });\nfor (const a of assignments) await deleteAssignment(a.id);\nawait deleteRole(roleId);","handlingStrategy":"validation","validationCode":"async function deleteRoleSafely(roleId: string) {\n  const assignments = await listAssignments({ role_id: roleId });\n  if (assignments.length > 0) {\n    throw new Error(`role still has ${assignments.length} assignment(s) — revoke first`);\n  }\n  return deleteRole(roleId);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await deleteRole(roleId);\n} catch (e) {\n  if (e.status === 409 && /assignments/.test(e.detail)) {\n    await revokeAssignmentsFor(roleId);\n    await deleteRole(roleId);\n  } else throw e;\n}","preventionTips":["In teardown scripts, always delete role assignments before roles","Surface 'N active assignments' in the UI before allowing a delete attempt"],"tags":["authz","api","roles","conflict","foreign-key","delete"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}