The Employee ID on the source account matches the Employee Number on the identity, but the account will not correlate. Sounds familiar?
An issue we encountered in our implementation involves an Active Directory account that should be a straightforward match: its employeeNumber is 12345, and Identity "User A", the current, active employee, carries that same employeeNumber. An unoptimized aggregation should correlate the two in one pass. Instead, after the AD source aggregates, the account sits uncorrelated. The SailPoint ISC UI shows it as an orphan, as if no identity in the tenant has employeeNumber 12345 at all. Why?
This investigation led us to looking up Hidden Identities. See SailPoint documentation here: Hidden Identities.
The mechanism follows directly from how SailPoint ISC evaluates correlation during aggregation. When the AD source aggregates, ISC does not correlate against "active identities" as a filtered set; it correlates against every Identity Cube that still exists in the tenant's database, visible or not. The sequence plays out like this:
A former employee's Identity Cube (call it "User A_Old") still exists in the database, because its deletion was blocked months ago and never revisited.
That hidden record was never cleared of its identity attributes when the person left. It still carries employeeNumber: 12345, the same number since reassigned, by HR policy or simple reuse, to a new or rehired person we're calling User A.
When the AD source aggregates a new or updated account for User A, ISC's correlation logic searches for an identity whose employeeNumber matches. Two Identity Cubes now satisfy that condition, the active User A and the hidden User A_Old, and the match resolves to the hidden one.
Because User A_Old is hidden from the UI and Search, the correlation succeeded from the database's point of view but is invisible from the administrator's. The AD account shows up as orphaned or uncorrelated, and User A's real identity never receives it.
The account isn't failing to correlate. It correlated perfectly, to a hidden identity.
The reason User A_Old was still there to intercept that match is the same one our earlier research surfaced: SailPoint enforces identity-level deletion protections that are entirely independent of the batch delete threshold on the authoritative source that made the record go hidden in the first place. Per SailPoint's own documentation, an identity cannot be deleted while any of the following is true:
ROLE_ADMIN, ORG_ADMIN, HELPDESK, and similar.Any one of these is enough to keep a hidden identity, and every correlation key it still holds, sitting in the database indefinitely. The identity doesn't need to be doing anything malicious or even noticeable; it just needs to still technically own one access profile, or still be listed as somebody's manager, quietly available to intercept the next aggregation that comes looking for that employeeNumber.
To make this diagnosable, we built a PowerShell auditor, Find-SailPointBlockedIdentities.ps1, that finds every identity with no account on your authoritative source(s) and checks all seven conditions above against each one.
It opens a small local connect form in your default browser, asking for tenant and Personal Access Token only, then runs the search, performs the additional API checks (pending approvals, elevated capabilities, active certifications), and produces a CSV: every orphaned identity, marked safe to delete or blocked, with the specific reason spelled out per record. When it finishes, it opens a second browser page summarizing the results with a direct download link for that CSV.
The tool never deletes anything itself; it only reports, which is what makes it safe to hand to an application owner or HR partner for sign-off before anyone touches DELETE /v3/identities/{id}.
Finding the report is the easy part; getting the right AD account onto the right identity is the point of running it. Once you suspect a correlation collision like the one above:
EmployeeId column for User A's employeeNumber. If a hidden record shares that value with the active identity, you have two Identity Cubes claiming the same key, the actual root cause, distinct from why deletion was blocked.BlockingReasons column is a remediation checklist for that one row: reassign ownership of anything it owns, reassign anyone reporting to it, resolve or reassign any pending work item, clear the protected flag if set, and let any active certification close or be reassigned. If it still holds accounts from other sources, decide per account whether those are themselves stale (disable or remove at the source) or genuinely still in use, in which case something beyond simple cleanup is required.DELETE /v3/identities/{id} releases employeeNumber 12345. The next unoptimized aggregation will correlate to exactly one Identity Cube, the active User A. This step is the actual fix; everything before it is prerequisite cleanup to reach it safely.Without this audit, a correlation collision like this one looks like a mystery to an ISC Admin: an account that "won't correlate" for no visible reason, on a source and identity pairing that should be unremarkable. The instinct is to re-check the correlation rule, re-run the aggregation, or open a case with SailPoint, none of which will find a record the UI itself can't show you.
The fix isn't a smarter correlation rule. Knowing exactly which of the seven conditions is holding a specific record in place turns "someone should look into this eventually" into a concrete, assignable remediation step.