Act on a cost finding
Turn an optimization-hunter finding into a defendable, executed saving, without leancosts ever touching your cloud.
1. Find the opportunity
Section titled “1. Find the opportunity”Findings surface in two converged places, both fed by the same hunter fan-out:
- Opportunities (
/opportunities), the daily landing and your team’s shared decision-of-record. Open findings, cost anomalies, and savings in flight all live here. It has two tabs: Triage (the findings) and Ledger (the signed change requests). The hero at the top sets the open savings against the last closed month’s bill: what comes back a year and the % of that bill it is, a bar of the bill with the recoverable slice lifted off it (captured / in flight / nobody on it), what waiting has already cost, and how much leaves every day until the findings are acted on. - Savings queue (
/hunters): one cross-category queue with filter facets for category (Compute, Database, Storage, Network, Commitments, Observability & AI), cloud, and finding kind, plus the standalone SQL-Migration hunter.
The Savings queue re-scans on its own: after every billing sync, and at least every six hours. Refresh forces a scan now: it hands the work to the background and gives you the button straight back, so you can keep working or close the tab. While a scan is running the header shows how long it has been going; when it finishes the queue reloads and the header shows the time it last ran. That status comes from the server, so you see a scan a teammate started too, and it survives a page reload. A full scan usually takes well under a minute, occasionally a few minutes on a large estate.
Low-confidence candidates are withheld by default: leancosts ships fewer, trustworthy numbers rather than a wrong one. Each finding carries an estimated monthly impact and a confidence score.
Check what was looked at before reading “no findings” as “nothing to
save”. Above the queue, Services looked at says how many of your
connected services the last scan actually answered for: “5 of 8 · 2 need
attention”. Open it for one tile per service: found something, nothing to
claim (a measured answer), blocked (a permission or input is missing, and
the tile names it), errored, or waiting for the first sweep. Only the first
two count as looked at. Click a tile to narrow the queue to that service; click
a blocked one to jump to the caveat that says what to grant. From an agent, the
same answer is get_hunter_coverage.
Every row names its cloud (the provider’s logo), what kind of finding it is in
plain words (“Advisor commitment purchase”, not advisor_commitment_purchase),
and which detector raised it (“via Azure Advisor”). Click a row’s finding-kind
chip (or pick from the Finding kind list) to see only that kind of finding;
the Addressable / month total follows the filter, and the scope is in the URL
so a copied link reopens on it.
On Opportunities, three controls help in a long list. Search finds a row
by resource, finding type, owner or its OPP- code. Under Filters (with
owner, status, source, type and confidence), the claim / mo slider
shows how many rows sit at each monthly amount: drag its left handle past the
small ones to focus on what is worth acting on (the export follows the range).
Group folds the list by finding type (the default) or by resource, each
group showing its row count and monthly claim: open a group to see its rows, or
pick No grouping for one flat list. Search and the slider hide nothing until
you use them, and each view is in the URL.
Most findings are savings (the bill goes down). Opportunities also surfaces a
small set of cost-adds: right-size-ups: a resource starved under sustained
load that should grow before it becomes an incident. These are a justified cost
increase, tracked through the same lifecycle. Use the Type filter
(Savings / Cost-adds) to focus on one or the other; cost-add rows are marked
+ $X/mo · review-only so you can never confuse a debit with a saving. The
headline number stays savings-only; committed cost-adds show on their own line.
A third kind of row is a cost anomaly, an unusual spike (or drop) versus a service’s usual monthly level. An anomaly is an alert to investigate, not a saving to claim: its amount is how far the month ran above (or below) that level, so it never counts toward your savings numbers. Its detail view says so (a “Spike vs. baseline” figure, its size when it was detected, plus a “Current pace vs. baseline” figure while a spike is still running, instead of the claim tiles) and the action is Acknowledge (you’ve seen it and looked into it), not Accept. The row shows the larger of the two figures. Use the Variance Explainer to find out what moved.
In the current month, a service is judged on what it has already spent plus its normal pace, weekday by weekday, for the days left. A spike therefore shows up a few days after it starts instead of at month end, and a quiet weekend or a charge billed once on the 1st does not set it off.
A month is compared with past months twice, as a total and day by day, and a row needs both comparisons to agree. A 28-day February is therefore not a drop, a flat monthly fee is not a spike, and the amount shown is the smaller of the two.
A spike from an earlier month stays on the list while at least five days of its latest full week still run above normal, with that week’s pace (the lower of its average and median day) as its amount per month. It leaves the list once three days of that week are back to normal.
On any finding you can Accept it (then draft a change request), set it aside with Not now (it comes back if the cost or usage materially worsens), or Won’t fix it permanently (it never re-surfaces). When several findings are the same kind, “Act on all N similar findings” drafts one change request that covers all of them: one approval instead of many.
2. See who owns what
Section titled “2. See who owns what”Ownership is per-finding, and the Ownership strip above the list rolls it up for the whole team: one row per person plus an Unassigned bucket, against To do, Doing, and Done (the last 90 days). Each cell shows how many findings and how much monthly claim sit there; click one to filter the list to that person and stage, click again to clear.
Assign from the list row itself (or from a finding’s detail view), the assignee is a picker of your organization’s members, so one person is always one bucket. A row nobody owns offers Claim: one click makes it yours. An owned row shows the owner’s initials and name. Cost anomalies and cost-adds are counted in a person’s cells but never added into a claim figure, because neither is a claimable saving.
Quoting a row to us
Section titled “Quoting a row to us”Every Opportunities row carries a short OPP-XXXXXX code; every hunter
recommendation carries a REC-XXXXXX one. Click either to copy it, and paste
it into your support message: it identifies the exact row without you sending a
title, a screenshot, or a URL. We can resolve either code, whichever one you
happen to have.
Hand a finding to a teammate
Section titled “Hand a finding to a teammate”Open a finding (or a resource) and its detail view has a Copy link button. The link points at that exact row, so a colleague who opens it lands on the same detail view instead of on an unscoped list: no “go to Opportunities, filter by compute, look for the plan called…” in chat.
The link is a pointer, not a grant: whoever opens it still has to be signed in to the same organization, and everything behind it is resolved under your tenant’s own access rules. A link handed to someone outside the organization shows them nothing.
3. Draft a change request
Section titled “3. Draft a change request”Every finding’s primary action drafts a change request (CR), the signed ledger entry that makes the action auditable. You can draft a CR from:
- a hunter finding (one CR, or “Act on all N similar” for one CR across the group), or
- the Copilot (when an answer is an actionable saving, it shows “Draft a change request”).
The CR is created in draft status with a written rationale, a scope, and the
estimated monthly impact (negative = savings, positive = a justified cost-add).
A right-size-up (cost-add) finding names the size it would move to and what that adds per month:
- An Azure VM, an EC2 instance or an RDS database: the next size up in its own family, and the difference between the two sizes’ on-demand prices (your negotiated rate where one applies; a Multi-AZ RDS database is priced on its own Multi-AZ rates). For an EC2 or RDS instance AWS Compute Optimizer finds CPU-underprovisioned, the size is the one AWS recommends instead, which can be two sizes up or another generation, and the finding says so. When the price list cannot price that step on the resource’s own rates (Windows or Spot, a Multi-AZ class the price list has no rate for, the top of a family), or AWS’s recommendation costs no more than the instance, no cost-add is listed: the resource’s Recommended configuration card still flags the load and says why there is no amount.
- A Compute Engine VM or a Cloud SQL instance: Google prices vCPUs and memory
rather than sizes, so both sizes are priced from those two on-demand rates in the
resource’s region: the next predefined machine type of the same family
(
n2-standard-4ton2-standard-8), or the next Cloud SQL tier (a custom tier with twice its vCPUs and memory). A custom or shared-core machine type, a Spot VM, an image with a paid licence (Windows Server, RHEL, a Marketplace appliance), a Confidential VM, a VM whose managed instance group membership could not be read (that read needs the optional Compute Viewer role), SQL Server and a database version in paid extended support list no cost-add, and the card says why. - An App Service Plan: the next tier of its plan ladder, at the plan’s current monthly bill, because each tier step about doubles the plan’s price.
When you draft a CR from one, leancosts asks you to name the target SKU / tier to grow to, because “how much bigger” is your call. The CR then carries the finding’s amount as a positive (cost-increase) impact and calls it the estimate for the size the finding names (one size up, or the size AWS recommended), since a different target changes it.
4. Submit → approve (dual control by default)
Section titled “4. Submit → approve (dual control by default)”The CR lifecycle is draft → submitted → approved → executed:
- Submit the draft. Approval is per-kind: a tag-only approver sees tag CRs to approve, an infra approver sees infra CRs.
- An approver in the allowlist (someone other than the author) signs off.
The signatures + rationale become the audit trail that later explains why a
month’s variance moved.
- One-person workspace? A governance admin can allow self-approval under Admin → Workspace → Approvals. The author then sees Approve my own request, confirms, and the timeline marks the CR self-approved. The approve capability is still required, and the change is audit-logged.
5. Apply the change yourself
Section titled “5. Apply the change yourself”leancosts never mutates your cloud. For tag changes it produces a guided CLI
kit (az tag, aws resourcegroupstaggingapi, gcloud); for infra changes it
records the decision. You run the change in your own environment, then open the
approved CR and click Record execution.
Record what actually happened
Section titled “Record what actually happened”Plans change at the keyboard. The dialog asks which of three things happened, and your answer decides what the Impact ledger claims:
| You chose | What it means | What happens next |
|---|---|---|
| Executed as proposed | The approved plan ran | The ledger drafts the approved estimate |
| Executed differently | You took another action: downsized instead of stopping, attached the IP instead of deleting it | You correct the pre-filled change rows and say why; the ledger claims your estimate and records both figures, so the bill grades what you did |
| Not applied | The team decided not to act | Nothing reaches the ledger. The CR closes as not applied, and the finding returns to the Savings Register as surfaced with your reason on its history, ready to be triaged again |
The last two require a short written reason, and the date can never be in the future. An execution is recorded once and cannot be edited: it is part of the signed audit trail. The approved plan itself is never rewritten: a modified execution is shown as approved vs executed, side by side.
The CR lifecycle statuses you will see are therefore draft → submitted → approved → executed, plus not applied (approved, then closed without acting),
rejected and cancelled.
Run it on a schedule instead of by hand
Section titled “Run it on a schedule instead of by hand”Once a change request is approved, its detail view offers an execution
bundle: the approved change packaged as one bash script you run with your
own credentials, in your own environment. leancosts generates the text: it
holds no write access to your cloud and never runs anything.
The script is self-documenting and defensive:
- a header with the change request id, the rationale, who approved it, and when the bundle was generated;
--dry-run: runs every check for real, prints the commands, changes nothing. Always start here;--rollback: the documented revert. When a change is genuinely one-way the script says so and refuses rather than inventing an undo;- pre-flight guards that refuse to run (exit code 2) if the CLI is missing or signed out, if a resource the change was approved against has since disappeared, if the setting the change touches no longer matches what was approved (where it can be re-read confidently, e.g. an instance approved for a stop that someone already stopped; the refusal names the field, the approved value, and what it found), if a value the recipe left to you isn’t exported, or if the bundle has gone stale (older than 30 days by default; regenerate it).
Alongside the script you get one-shot scheduler templates (a crontab line, a GitHub Actions workflow, and an Azure DevOps pipeline) so you can pin the run to a change window. They schedule the change once; a change request is a single signed decision, so nothing here loops.
Reporting back (optional). By default the run prints a receipt and you mark
the change request executed yourself. If you’d rather it stamped the request for
you, generate a report-back token from the same panel and store it in your
CI secret store as LEANCOSTS_RECEIPT_TOKEN. That token is not an API key: it
is scoped to that one change request, it can only mark that one change request
executed, it expires, and it is never written into the script or the templates.
Re-running a job that already reported back changes nothing.
One token is live per change request: generating another replaces it, and Revoke in the same panel kills the live one immediately.
6. Prove the saving
Section titled “6. Prove the saving”Once executed, post the saving to the Realized Savings Ledger (sidebar: Savings): each claimed saving must trace back to its executed CR and a measured before/after cost delta, which leancosts measures from the bill over a closed evidence window when you post. Claims without evidence cannot be posted, and a figure typed without a window is shown as attested and never counted. For the exact criteria a saving clears, see How we count a saving. The ledger behaves like a bank account: a realized right-size-up posts as a measured cost increase that nets against your savings, so the number finance sees is the honest total. The ledger then feeds the ROI line in your email digest and the Savings Register’s savings report.
Already fixed it yourself? Say so
Section titled “Already fixed it yourself? Say so”You don’t have to go through a change request to get credit. When a scan stops detecting an opportunity, leancosts closes the row as auto-resolved, and, when the disappearance plausibly follows our recommendation, asks you one question. A banner on Opportunities reads “N opportunities were resolved. Confirm how.”; Review lists only those rows, each marked Awaiting answer. Open one, and its drawer offers two answers:
- We applied this fix: the row is marked done and the saving is drafted into the ledger as a claim. It is a claim, not a posted saving: the same bill measurement grades it once the first full month after your fix closes.
- Resolved independently: an unrelated change made it go away. Nothing is claimed. leancosts will not credit itself with a saving you say wasn’t ours.
The loop, end to end
Section titled “The loop, end to end”hunter finding ─────────► draft CR ──► submit ──► approve ──► you execute ──► realized-savings ledger (Savings Register/Hunters) (rationale + impact) (dual control) (guided CLI kit) (evidence-backed) └──────────────────────► you already fixed it ──► confirm how ──► claim (measured from the bill)