Data Purger role not honored by Log Analytics Purge API

Jeremy Cahill 20 Reputation points Microsoft Employee
2026-06-17T23:10:05.0533333+00:00

Summary

The Log Analytics workspace purge API returns a 400 BadRequest with error code QueryValidationError stating that the calling identity does not have the "Data Purger" role, despite the role being correctly assigned to the identity on the workspace resource. This issue affects multiple workspaces and identities in the same subscription.

Environment Details

  • API Version: 2025-07-01
  • Issue start date: At least 2026-05-31 (may be earlier)
  • Affected resources: Three separate Log Analytics workspaces across two resource groups, using two different user-assigned managed identities

Exact Error Response


HTTP 400 Bad Request

{

  "error": {

    "message": "The request had some invalid properties",

    "code": "BadArgumentError",

    "innererror": {

      "code": "QueryValidationError",

      "message": "The user with object Id '<principal-id>' does not have the role 'Data Purger' required to perform purge operation on this resource"

    }

  }

}

Correlation ID: b296efb3-3bb3-461d-9784-d02c3b93f4fe

API Call DetailsRequest:```

POST https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}/purge?api-version=2025-07-01


**Authentication:** Bearer token acquired via MSAL `ManagedIdentityApplicationBuilder` using a user-assigned managed identity's Client ID with scope `https://management.azure.com/.default`.

**Request Body:**```json

{

  "table": "AppTraces",

  "filters": [

    {

      "column": "TimeGenerated",

      "operator": "<",

      "value": "<cutoff-datetime>"

    },

    {

      "column": "Properties",

      "operator": "==",

      "value": "<marker-value>",

      "key": "<custom-dimension-name>"

    }

  ]

}

What Has Been Verified

  1. Role is correctly assigned: The "Data Purger" role (role definition ID 150f5e0c-0603-4f03-8c7f-cf70034c4e90) is assigned to each identity directly on the respective Log Analytics workspace resource. This has been confirmed visually in the Azure portal under Access control (IAM) → Role assignments.
  2. Role assignment scope is correct: The assignment is scoped to the Log Analytics workspace itself (not a parent resource group or subscription).
  3. Principal ID matches: The Object (Principal) ID reported in the error message matches the actual Object ID of the managed identity as shown in the Azure portal.
  4. Access control mode: The workspace access control mode is set to "Use resource or workspace permissions".
  5. No blocking deny assignments: The only deny assignment present denies Microsoft.Authorization/roleAssignments/write only and does not block purge operations.
  6. Infrastructure deployed via Bicep: The role assignment is managed declaratively via Azure Bicep. The most recent deployment succeeded. The Bicep template uses principalType: 'ServicePrincipal' and scopes the assignment to the Log Analytics workspace resource.
  7. Identity is assigned to the function app: The user-assigned managed identity is included in the function app's identity configuration. Token acquisition succeeds (the function obtains a valid bearer token before making the purge call).
  8. Other roles on the same identity work: The same identity has "Monitoring Reader" assigned on the App Insights resource, and the query operation (which uses the same identity with a different scope) succeeds — the function successfully counts messages before attempting the purge.
  9. Manual role re-add attempted: For one of the affected workspaces, the Data Purger role assignment was manually removed and re-added via the portal. The issue persists.
  10. Reproducible across environments: The issue occurs identically across three separate workspaces (two production, one test) using two different managed identities, ruling out an identity-specific or workspace-specific configuration issue.
Azure Monitor
Azure Monitor

An Azure service that is used to collect, analyze, and act on telemetry data from Azure and on-premises environments.


Answer accepted by question author
Suchitra Suregaunkar 16,780 Reputation points Microsoft External Staff Moderator
2026-06-18T00:47:32.08+00:00

Hello Jeremy Cahill,

Thanks for the very detailed write-up, the symptoms point to something that is almost always mis-diagnosed as an RBAC misconfiguration. The Log Analytics purge endpoint wraps the authorization decision inside a QueryValidationError payload, but the underlying check is still a real RBAC evaluation against the caller's bearer token claims, not against the current role-assignment state in ARM. That distinction is what usually causes this exact failure pattern.

Most likely root cause: stale / cached token on the user-assigned managed identity.

When you acquire a token via MSAL's ManagedIdentityApplicationBuilder for https://management.azure.com/.default, the token is cached (both by IMDS/the managed identity service and by MSAL's in-memory cache in the Function host). Tokens are typically valid for ~24 hours, and the IMDS endpoint will continue to hand back a cached token until it is close to expiry.

The purge service performs its RBAC check against the oid / claims in the presented token, not by re-querying ARM at request time. So if the Data Purger role was assigned after the token currently being used was first issued, the cached token does not reflect the new role and the purge backend correctly reports "does not have the role 'Data Purger'" even though the assignment is visibly correct in the portal.

This matches your symptoms exactly:

  • The query operation (which uses a different RBAC-checked permission that was already on the token when it was first cached, e.g., via Monitoring Reader on App Insights) succeeds.
  • The purge call (which requires Microsoft.OperationalInsights/workspaces/purge/action, granted by the Data Purger built-in role, role ID 150f5e0c-0603-4f03-8c7f-cf70034c4e90) fails.

Reference: https://learn.microsoft.com/en-us/azure/azure-monitor/logs/personal-data-mgmt

  • Removing and re-adding the assignment on the portal does not help because the running Function instance is still holding the same cached token.
  • It is reproducible across multiple workspaces / identities because every one of those identities has the same "first-token-acquired-before-role-was-granted" condition.

Please have a look into below provided actions to confirm and fix:

  1. Decode the bearer token your Function is sending (paste it on jwt.ms) and verify:
    • oid matches the user-assigned managed identity's Object (Principal) ID.
    • aud is https://management.azure.com/.
    • iat (issued-at) is before the time you (re)assigned the Data Purger role.
  2. Force a fresh token acquisition. Easiest options:
    • Restart the Function App (Stop → Start, not just Restart from VS Code) so the MSAL in-process cache is cleared.
    • Or, in code, call the managed-identity token provider with forceRefresh: true (or use TokenRequestContext with ForceRefresh = true for Azure.Identity) so MSAL bypasses its cache and re-acquires a token from IMDS.
    • As a quick sanity check, re-deploy / swap the slot , this also forces a new worker process and a new token.
  3. After the new token is issued, re-run the purge. The 400 / QueryValidationError should disappear.

Secondary things worth double-checking (in priority order):

Even after fixing the token cache, a small set of conditions can still cause QueryValidationError on AppTraces. Quickly validating them avoids a second round trip:

  • Filter shape for the Properties column on AppTraces. The purge API allows a key on dynamic columns (this is how customDimensions/Properties is targeted), but only the documented operators are accepted on that combination. Run the equivalent Kusto in Logs first to confirm the predicate is well-formed and returns the expected rows,
       AppTraces
       | where TimeGenerated < datetime(<cutoff-datetime>)
       | where tostring(Properties["<custom-dimension-name>"]) == "<marker-value>"
    
    If the KQL does not return rows or the dynamic field name is mis-cased, the purge engine will reject the predicate.
  • GDPR-compliance constraint. Per the official REST API reference, "Log Analytics only supports purge operations required for compliance with GDPR. The Log Analytics product team reserves the right to reject requests for purge operations that are not for the purpose of GDPR compliance." The validation layer can surface this as a QueryValidationError. If the use case is not GDPR-driven (it's cost / cleanup of telemetry), the recommended approach is to use table-level retention or the Delete Data API instead of purge.
  • Confirm the role is the built-in Data Purger, not a custom role, and that the principalId on the assignment is the managed identity's object ID (not the App Registration / Enterprise App appId). Your Bicep uses principalType: 'ServicePrincipal', which is correct for a user-assigned MI, but it's worth double-checking the principalId parameter resolves to the MI's principalId, not its clientId.
  • Throttling / batching. Purge is throttled at 50 requests/hour per workspace; predicates should be batched. Not a cause of QueryValidationError, but worth knowing if you start automating retries.

Please have a look into below recommended alternative if this isn't GDPR-driven:

If the purpose of this job is operational cleanup (e.g., trimming AppTraces), the supported and lower-risk path is:

  • Delete Data API (tables/{table}/deleteData, api-version 2023-09-01) — requires only the Log Analytics Contributor built-in role, supports TimeGenerated and other filters, and is the documented "ideal for unplanned deletions of individual records" path.
  • Or simply set table-level retention on AppTraces so old rows age out automatically.

Purge is intentionally constrained to GDPR because, as the docs call out, "Delete and purge operations are destructive and non-reversible! Use extreme caution in their execution."

References

Could you do the following and share the result?

  1. Restart the Function App and immediately re-run the purge (a single retry).
  2. If it still fails, capture the new bearer token from the failing call, paste it on jwt.ms, and confirm the iat timestamp is after the most recent role assignment time on that workspace.

That single data point will confirm whether this is the cached-token scenario or whether we need to dig into the predicate-validation / GDPR-rejection path next.

Thanks,

Suchitra.

Was this answer helpful?

1 person found this answer helpful.
0 comments No comments

0 additional answers

Sort by: Newest

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.