Open the Access control (IAM) blade on any subscription that has been around for a few years and you will find them: role assignments labeled "Identity not found". Every time a user, group, service principal, or managed identity is deleted, its role assignments are left behind as orphans. Add the assignments still granted to departed employees, and the over-broad grants nobody has reviewed since a long-finished project, and RBAC quietly becomes the least tidy part of the tenant. The orphans are not just clutter: they count against the hard 4,000 role-assignments-per-subscription limit, which can silently block new grants when you hit it.
Why Azure cannot clean this up for you
- Deleting an identity does not delete its role assignments. The assignment stays behind, pointing at a principal ID that no longer resolves, which is exactly what the portal renders as "Identity not found".
- There is no native bulk cleanup. The portal lets you remove orphaned assignments one at a time, per scope. Nothing sweeps a whole subscription for you on a schedule.
- The 4,000 limit is per subscription and it is hard. Orphaned assignments count toward it just like live ones, and there is no built-in warning as you approach the ceiling; the first symptom is often a failed role assignment in a deployment pipeline.
- Nothing tells you an assignment went stale. Azure does not notify anyone when a principal behind an assignment disappears, so the orphans accumulate unseen until someone happens to open the IAM blade.
The DIY approach: resolve every principal, then prune
The pattern the PowerShell blogs converge on is the same one you would build yourself:
- List every role assignment in the subscription. In PowerShell,
Get-AzRoleAssignmentexposes orphans as entries whoseObjectTypeisUnknown. - Verify against the directory. For a reliable answer, resolve the principal
IDs through Microsoft Graph (for example
directoryObjects/getByIds) and treat an assignment as orphaned only when the directory positively confirms the principal is gone. - Remove the confirmed orphans with
Remove-AzRoleAssignment, and while you are in there, review grants to departed users and roles that are broader than the job requires. - Schedule it. A one-off script cleans up today's mess; identities get deleted every week, so the sweep needs to run on a timer to stay clean.
The dangerous part is the edge cases. If a Graph lookup fails mid-run (throttling, an outage, a propagation delay), a naive script reads the missing answer as "principal gone" and deletes live assignments. Assignments inherited from a management group, or belonging to foreign-tenant principals under Azure Lighthouse, also need to be recognized and left alone. A safe version of this script is mostly error handling.
A ready-made option: RBAC Janitor
RBAC Janitor is that pattern packaged, with the error handling built in. Every day it lists the
subscription's role assignments, confirms each principal against Microsoft Graph, reports the
orphans and the assignment count versus the 4,000 limit to a Teams channel via an incoming
webhook, and, if you enable cleanup, deletes the confirmed orphans. It ships in dry-run,
report-only mode by default; deletion requires both explicitly turning it on and granting the
delete role. It fails closed (a principal that cannot be verified is reported, never deleted),
aborts and alerts if an implausibly large share of assignments suddenly looks orphaned, and
excludes management-group-inherited and Azure Lighthouse foreign-tenant assignments from
deletion entirely. Report-only mode needs just Reader plus Graph Directory.Read.All.
Key facts
- Orphaned assignments: deleting an identity leaves its role assignments behind as "Identity not found" entries.
- The limit: 4,000 role assignments per subscription, hard, and orphans count against it.
- Native bulk cleanup: none; removal is manual, per assignment, per scope.
- Safe automation: verify principals via Microsoft Graph and only delete on a positive "gone" answer; treat lookup failures as unknown.
- Leave alone: assignments inherited from management groups and foreign-tenant (Azure Lighthouse) principals.
Related reading: just-in-time Azure role elevation without PIM.
Written by the team at Katabarwa Labs. We build small, single-purpose Azure tools that run entirely inside your own tenant. Questions: abaho@llmgraph.ai.