# Cost Investigation

> 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

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

---


# 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:

```kusto
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:

```kusto
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.

