Cost Investigation
Explain a cost spike or unexpected charge. This skill focuses on what changed and why. For "what should I do about it" (right-sizing, cleanup, commitments) hand off to cost-optimization.
Note: The investigation uses cost data and Azure Resource Graph (resource inventory + change history) only — apply findings with normal change-control review.
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 viaResourceContainers | 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_queryfirst — it confirms the syntax and property paths before you spend a call onexecute_query. If you need to adapt a query to a different resource type or field, draft it withgenerate_query(a natural-language prompt), then validate and execute.
Step 1: Find the Spike Window
Call query_costs with timeframe=MonthToDate, granularity=Daily on the user's scope. Identify the day(s) where daily cost jumped materially above the prior daily run rate. Report the spike date(s) and the magnitude ($ jump and % above baseline).
Step 2: Which Service Drove It
Run two queries to compare current month vs the same number of days in the prior month:
query_costswith timeframe=MonthToDate, groupBy=ServiceNamequery_costswith timeframe=Custom for the same number of days in the prior month (e.g., today is the 12th → query days 1–12 of last month), groupBy=ServiceName
Rank services by absolute $ delta. Identify the top 1–3 contributors to the increase. Also call query_costs with groupBy=ChargeType to flag any "Purchase" rows — one-time charges (reservations, marketplace, support) shouldn't be reported as recurring growth.
Multi-currency caveat: at management-group or billing-account scope, the result may contain multiple
Currencyvalues. Compute deltas per currency — don't combine USD and GBP into one number.
Step 3: What Changed (Resource Graph)
For the top service from Step 2, call execute_query with this Azure Resource Graph query against resourcechanges to find Create / Update / Delete events that landed near the spike window:
resourcechanges
| where subscriptionId in ('<sub-id-1>', '<sub-id-2>')
| where properties.targetResourceType =~ '<resource-type-from-step-2>'
| where todatetime(properties.changeAttributes.timestamp) > ago(14d)
| project targetResource=tostring(properties.targetResourceId),
changeType=tostring(properties.changeType),
timestamp=todatetime(properties.changeAttributes.timestamp),
changedBy=tostring(properties.changeAttributes.changedBy)
| order by timestamp desc
| limit 50
For Update rows, inspect properties.propertyChanges (an array of {propertyName, previousValue, newValue}) to spot SKU upgrades, tier changes, or scale-out events — these drive cost without creating new resources.
⚠️
resourcechangesretains change history for ~14 days, and visibility depends on tenant permissions. An empty result does not prove nothing changed — the change may predate the 14-day retention window or not be visible at this scope.
Step 4: Identify the Cost Owner
Call execute_query to surface the owners of the top spiking resources via subscription / RG / tags:
Resources
| where type =~ '<resource-type-from-step-2>'
| where subscriptionId in ('<sub-id-1>', '<sub-id-2>')
| summarize count() by subscriptionId, resourceGroup, tostring(tags.Owner), tostring(tags.CostCenter)
| order by count_ desc
| limit 20
Tag-key lookups in Resource Graph are case-sensitive (
tags.CostCenterwon't match acostcenterkey), and tag names vary by organization. Use the scope's actual cost-allocation tag names — ifOwner/CostCentercome back empty, the resources may use different keys (e.g.env,team) rather than being untagged.
Use this to point the conversation at a specific team, owner, or cost center.
Step 5: Classify and Recommend Next Step
Based on Steps 1–4, classify the spike:
- Recurring vs one-time — Did a workload scale up permanently, or was this a one-time purchase / batch / deployment?
- Controllable vs expected — Can the team take action (right-size, clean up, exchange a reservation), or is it expected growth?
If a Purchase row in Step 2 explains most of the delta → it's a one-time charge; nothing to fix.
If a commitment recently expired (cost rose, amortized stayed similar) → suggest the commitment-analysis skill.
If resources scaled up, new resources were created, or the workload looks over-provisioned → offer to hand off to cost-optimization for remediation:
Want me to look at what can be right-sized, cleaned up, or covered by a commitment? I can run the cost-optimization skill on the same scope.
AKS callout
If AKS appears in the top services and the user wants a cluster/namespace breakdown, hand off to the aks-cost-analysis skill — it covers cluster-level, namespace-level, and utilization-bucket breakdowns and includes the AKS Cost Analysis enablement check.
Presentation
- Spike summary — Date(s), magnitude ($ and % above baseline), top service(s)
- What changed — Key Create / Update / Delete events from
resourcechangesduring the window, withchangedBywhere available - Cost owner — RG / subscription / cost-center tag responsible for the top spiking resources
- Classification — Recurring vs one-time, controllable vs expected
- Next step — One specific recommendation: hand off to cost-optimization / commitment-analysis, or "no action — one-time charge"
If
query_costsresults hit the row cap or look truncated, mention that exporting the dataset (Cost Management → Exports) gives full fidelity for deeper analysis.