| name | cost-investigation |
| description | Explain a cost spike or unexpected charge: when it happened, which service drove it, what changed, and who owns the resources. WHEN: "why did cost go up", "cost spike", "unexpected charge", "explain increase", "bill higher than expected", "surprise cost". INVOKES: query_costs, generate_query, validate_query, execute_query |
| license | MIT |
| metadata | {"version":"1.0.0"} |
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 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: 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_costs with timeframe=MonthToDate, groupBy=ServiceName
query_costs with 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 Currency values. 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.
⚠️ resourcechanges retains 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.CostCenter won't match a costcenter key), and tag names vary by organization. Use the scope's actual cost-allocation tag names — if Owner / CostCenter come 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
resourcechanges during the window, with changedBy where 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_costs results hit the row cap or look truncated, mention that exporting the dataset (Cost Management → Exports) gives full fidelity for deeper analysis.