# Cost Guardrails

> Check Azure Policy SKU/region restrictions, tag compliance, and budget coverage via Azure Resource Graph. WHEN: "what SKUs are allowed", "can I deploy this", "check policy", "allowed VMs", "will this be denied", "tag compliance", "budget coverage", "governance health". INVOKES: generate_query, validate_query, execute_query, list_budgets

- Skill: `azure-samples/cost-guardrails` (Agent Skill)
- Install (CLI): `npx skillmds@latest add azure-samples/cost-guardrails`
- Raw SKILL.md: https://api.skillmd.com/api/skills/azure-samples/cost-guardrails/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- License: MIT
- Author: Azure-Samples (https://skillmd.com/u/azure-samples)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/azure-samples/cost-guardrails

---


# Cost Governance & Guardrails

Check Azure Policy cost guardrails (SKU/region restrictions), cost-allocation tag compliance, and budget coverage across a scope. All policy lookups go through Azure Resource Graph (`policyresources` table).

> ARG queries against `policyresources` require read access at the scope. Empty results may mean the caller lacks visibility into assigned policies — not that none exist.

> All ARG queries below take subscription-id placeholders (`<sub-id-1>, <sub-id-2>`) — replace with the target subscriptions. To target a management group, first resolve member subs via `ResourceContainers | where type == 'microsoft.resources/subscriptions' | where properties.managementGroupAncestorsChain has '<mg-name>' | project subscriptionId`.

> **Run every ARG query through the generate → validate → execute workflow.** The queries below are ready to run, but pass each one to `validate_query` first — it confirms the syntax and property paths before you spend a call on `execute_query`. If you need to adapt a query to a different resource type or field, draft it with `generate_query` (a natural-language prompt), then validate and execute.

## Step 1: List Policy Assignments

Call `execute_query` to enumerate policy assignments and read their parameters. Don't filter by `subscriptionId` — MG- and tenant-level assignments cascade down to subscriptions, and filtering would drop them.

```kusto
policyresources
| where type =~ 'microsoft.authorization/policyassignments'
| project assignmentName = name,
          displayName = tostring(properties.displayName),
          scope = tostring(properties.scope),
          enforcementMode = tostring(properties.enforcementMode),
          parameters = properties.parameters
| limit 100
```

The chosen guardrail values live in `parameters` (e.g. `listOfAllowedSKUs`, `listOfAllowedLocations`). The policy definition only holds the schema and defaults, so there's no need to join it.

## Step 2: Filter to SKU / Region Restrictions

Re-filter the Step 1 result (or re-run with this filter appended) for the policies that restrict SKUs or regions:

```kusto
... | where displayName has 'sku' or displayName has 'location'
      or isnotempty(parameters.listOfAllowedSKUs)
      or isnotempty(parameters.listOfAllowedLocations)
```

Read the allowed values straight from `parameters`:

- `{ "listOfAllowedSKUs": { "value": ["Standard_B2s", ...] } }`
- `{ "listOfAllowedLocations": { "value": ["eastus", "westeurope"] } }`

Also check `enforcementMode` before reporting a restriction as binding: `Default` means the assignment is enforced (a `Deny` policy blocks non-compliant deployments); `DoNotEnforce` means it's evaluated for compliance only and does **not** block deployments.

If nothing returns: "No SKU or region restrictions detected on this scope — all SKUs and regions are allowed."

## Step 3: Tag Compliance

Check coverage on standard cost-allocation tags via `execute_query` with this Azure Resource Graph query. Default to `CostCenter` / `Owner` / `Environment`; adjust to the user's actual standard if they have one.

```kusto
Resources
| where subscriptionId in ('<sub-id-1>', '<sub-id-2>')
| where isnull(tags.CostCenter) or isnull(tags.Owner) or isnull(tags.Environment)
| summarize missingRequiredTag=count() by type
| order by missingRequiredTag desc
| limit 10
```

> Tag-key lookups in Resource Graph are case-sensitive (`tags.CostCenter` won't match a `costcenter` key), so use the exact casing of the organization's tag names to avoid over-reporting.

## Step 4: Budget Coverage

Call `list_budgets` on the scope. List which subscriptions / resource groups have a budget and which don't. For setting or reviewing budgets, point to the **budget-setup** and **budget-health** skills.

## Presentation

- **Allowed SKUs** *(if the user asked about a specific resource type)* — list the permitted SKUs from Step 2. For pricing on those SKUs, point to the **pricing-estimate** skill.
- **Tag compliance** — Top resource types missing the standard cost-allocation tags.
- **Budget coverage** — Scopes covered vs uncovered.

> Defaults shown (standard cost-allocation tags) are a starting point — adjust to your organization's standards.

## Example

User: *"Can I deploy a Standard_E64s_v5 VM?"*

1. Run Steps 1–2 → parse `listOfAllowedSKUs` and `enforcementMode` from the relevant assignment.
2. Reply: *"`Standard_E64s_v5` is not in the allowed list (`Standard_B2s`, `Standard_D2s_v5`, `Standard_D4s_v5`). The assignment is enforced (`enforcementMode = Default`), so Azure Policy would deny the deployment. For pricing on the allowed SKUs, run the **pricing-estimate** skill."*

> If `enforcementMode = DoNotEnforce`, say the deployment would be flagged non-compliant but **not** blocked.

