Cloud FinOps skill for Codex

Give every cloud dollar an owner and a trend. Codex builds the cost operating model before cutting anything: billing exports in FOCUS format, a small enforced tag policy, budgets with forecast alerts, anomaly routing to owning teams and a unit cost per product. A bundled script measures untagged spend from any billing CSV and can gate a weekly job, and commitment advice is sized from the measured floor with purchases left to you.

  • Cloud Platforms
  • $cloud-finops
  • 176 lines
  • 3 files
  • ~2,000 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- cloud-finops

Installs to ~/.agents/skills/cloud-finops/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $cloud-finops, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Sets up cloud cost management across AWS, Google Cloud and Azure - billing exports in the FOCUS format, a tagging and labeling policy with enforcement, showback by team and environment, budgets and anomaly alerts, unit cost metrics, commitment planning for Savings Plans, reservations and committed use discounts, and Infracost checks on infrastructure PRs. Use when nobody knows who owns the cloud bill, spend jumped unexpectedly, tags are missing, finance asks for cost per customer, or someone proposes buying commitments.

Use it when

  • The cloud bill has no clear owners or showback by team
  • Spend spiked and you need budgets and anomaly alerts
  • Tags or labels are inconsistent across accounts and IaC
  • Someone wants to buy Savings Plans, reservations or committed use discounts

Use something else when

How to check the result

Run scripts/tag_coverage.py on the latest export to record untagged share, send a test budget alert to the real channel, and reconcile showback totals with the invoice.

The mistake it prevents

Activating AWS cost allocation tags late; they are not retroactive, so months of spend stay unattributable.

~/.agents/skills/cloud-finops/SKILL.md

--- name: cloud-finops description: Sets up cloud cost management across AWS, Google Cloud and Azure - billing exports in the FOCUS format, a tagging and labeling policy with… license: MIT ---

Cost work is done when every dollar has an owner, spend surprises are caught within a day instead of at the month-end invoice, teams see a unit cost they can act on, and commitments are bought from measured baselines, not hopes. Visibility comes first; cutting without allocation just moves the argument.

This skill covers the operating model across providers. For hands-on AWS savings (rightsizing, storage classes, NAT and egress fixes) hand off to aws-cost-optimization.

Inspect first

  1. Read AGENTS.md and the IaC (Terraform, Pulumi, CDK, Bicep) to see whether tags or labels are applied centrally (default_tags, stack tags) or per resource.
  2. Find what billing data exists: AWS Data Exports (CUR 2.0 or FOCUS), GCP billing export to BigQuery, Azure Cost Management exports. Most queries need network and billing read permissions; ask the user for an exported CSV if you cannot reach the console.
  3. Ask for the account or project structure (AWS Organizations OUs, GCP folders, Azure management groups) since it is the cheapest allocation dimension.
  4. Never purchase commitments, change budgets that trigger automated actions, or delete resources on your own. Those require explicit user approval.

Maturity checklist

  • Billing export enabled to a queryable store, ideally in FOCUS columns
  • Account/project structure maps to teams or products
  • Tag policy written, applied by IaC defaults, enforced in CI and by provider policy
  • Untagged spend under 10% of total, tracked weekly
  • Budgets per account/team with alerts at 50/80/100% of forecast
  • Anomaly detection on, routed to the owning team’s channel
  • Monthly showback report per team with trend and top movers
  • One unit metric per product (cost per active customer, per 1k requests, per GB processed)
  • Commitment coverage and utilization reviewed monthly
  • Infracost (or similar) comments cost deltas on infrastructure PRs

Tagging policy

Keep it small. Four required keys beat twelve that nobody fills in.

KeyExamplePurpose
ownerteam-paymentsWho gets the alert and the bill
envprod, staging, devSeparate production from waste candidates
servicecheckout-apiProduct or component
cost-centercc-1042Finance mapping

Rules:

  • Lowercase keys and values with hyphens. GCP labels allow only lowercase, digits, - and _, so a cross-cloud policy should follow GCP’s stricter rules.
  • Apply tags centrally: Terraform AWS provider default_tags, Pulumi stack transformations, Bicep parameters passed to every module.
  • AWS user-defined cost allocation tags must be activated in the Billing console before they appear in cost data, and they are not retroactive. Activate them on day one.
  • Some costs can never be tagged (support plans, some data transfer, marketplace fees). Allocate them by a documented rule (proportional to tagged spend) instead of chasing them.
provider "aws" {
  region = "us-east-1"
  default_tags {
    tags = { owner = var.owner, env = var.env, service = var.service, "cost-center" = var.cost_center }
  }
}

Enforce in layers: CI lint on IaC (tflint rules, OPA or Checkov policies), AWS Tag Policies or Azure Policy require tag for creation-time checks, and the coverage report below for anything that slipped through.

Measuring tag coverage

python3 scripts/tag_coverage.py focus_export.csv --required owner,env,service --max-untagged 10

It reads FOCUS exports (JSON Tags column, EffectiveCost, ServiceName) by default; for legacy exports map tag columns with --tag-column owner=resource_tags_user_owner and --cost-col. It prints the untagged share per key and the services contributing most untagged cost, and exits 1 when the share exceeds --max-untagged, so it can gate a weekly job.

Budgets and anomalies

ProviderBudgetsAnomalies
AWSAWS Budgets (cost, usage, coverage, utilization), alerts by email or SNSCost Anomaly Detection monitors per service, account or tag
Google CloudBilling budgets with threshold rules and Pub/Sub notificationsCost anomaly detection in Billing
AzureCost Management budgets with action groupsCost Management anomaly alerts

Budgets alert; they do not cap spend. A hard stop requires automation (for example a Pub/Sub-triggered function that disables billing on a sandbox project), which is destructive and must be scoped to non-production with explicit approval.

Alert on forecasted spend as well as actual so you get warned mid-month.

Unit economics

Pick one metric per product that engineering can influence:

cost per 1k API requests = (allocated service cost for the month) / (requests / 1000)
cost per active customer = (product cost incl. shared allocation) / (monthly active customers)

Report the trend, not the absolute. A rising total with falling unit cost is healthy growth; a flat total with rising unit cost is a regression.

Commitments

InstrumentFlexibilityUse for
AWS Compute Savings PlansAny region, instance family, Fargate, LambdaDefault for steady compute
AWS EC2 Instance Savings Plans / RIsLocked to family and regionVery stable, known fleets
AWS RDS / ElastiCache / OpenSearch RIsEngine and family specificDatabases that will not change soon
GCP committed use discounts (resource-based or spend-based)Region and resource type, or service spendSteady Compute Engine, Cloud SQL, Cloud Run
Azure reservations / Azure savings plan for computeSpecific SKU vs flexible computeSame split as AWS

Procedure:

  1. Measure the baseline: the lowest hourly on-demand spend over the last 30 to 90 days, after rightsizing. Commit to cover the floor, not the average.
  2. Target roughly 70 to 85% coverage of steady compute; leave the rest on-demand for change.
  3. Prefer 1-year no-upfront terms unless the workload is certain for 3 years.
  4. Track utilization monthly; under 95% means you over-committed.
  5. Present the purchase as a recommendation with numbers. The user or finance buys it. Never execute a purchase.

PR-time cost checks

infracost breakdown --path . --format json --out-file /tmp/base.json
infracost diff --path . --compare-to /tmp/base.json

Infracost needs an API key and network for pricing data. Post the diff as a PR comment so reviewers see ”+$410/month” next to the change that causes it.

Verification

  • Coverage script run on the latest export; untagged share and top offenders recorded.
  • Budget and anomaly alerts fire to a real channel (send a test notification).
  • Showback totals reconcile with the invoice within a small tolerance after allocating untaggable costs.
  • Commitment recommendations cite baseline, coverage and break-even.

Output: monthly cost report

## Cloud cost report: <month>
- Total: $X (vs last month +/-Y%, vs budget Z%)
- Untagged share: N% (target < 10%); top untagged: <service, $>
- By team: table of owner, cost, change, top driver
- Unit cost: <metric> = $A (trend)
- Anomalies: <date, service, $ impact, cause, owner, status>
- Commitments: coverage C%, utilization U%, expiring in next 90 days
- Actions: <owner, action, expected monthly saving>

Gotchas

  • Comparing unblended cost month to month hides commitment effects; use amortized or FOCUS EffectiveCost for allocation and showback.
  • Shared clusters (Kubernetes) need in-cluster allocation (OpenCost or Kubecost) by namespace or label; cloud tags stop at the node.
  • Data transfer is usually the least visible line and the hardest to tag; break it down by usage type before blaming a team.
  • Deleting “idle” resources found in cost data needs owner confirmation; a zero-traffic resource may be a disaster recovery standby.

What the zip contains

Codex reads bundled files only when a step points to them. Every one is shown here, so you can review it before installing.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Cloud FinOps"
  short_description: "Give every cloud dollar an owner and a trend"
  default_prompt: "$cloud-finops Our AWS and GCP bill doubled and nobody knows which team caused it, set up cost allocation"
scripts/tag_coverage.py122 lines, 4.8 KB
#!/usr/bin/env python3
"""Report cost allocation tag coverage from a billing export CSV.

Works with FOCUS exports (a JSON "Tags" column) out of the box, and with exports
that store each tag in its own column via --tag-column key=ColumnName.

Usage:
  python3 tag_coverage.py export.csv --required owner,env,cost-center
  python3 tag_coverage.py cur.csv --cost-col line_item_unblended_cost \
      --group-col line_item_product_code \
      --tag-column owner=resource_tags_user_owner --tag-column env=resource_tags_user_env \
      --required owner,env --max-untagged 10

Exit codes: 0 ok, 1 untagged share above --max-untagged, 2 bad input.
"""
from __future__ import annotations

import argparse
import csv
import json
import sys
from collections import defaultdict
from decimal import Decimal, InvalidOperation


def parse_args() -> argparse.Namespace:
    p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    p.add_argument("csv", help="billing export CSV (FOCUS or provider-specific)")
    p.add_argument("--required", required=True, help="comma-separated tag keys every cost should carry")
    p.add_argument("--cost-col", default="EffectiveCost", help="cost column (default EffectiveCost)")
    p.add_argument("--group-col", default="ServiceName", help="column to break untagged cost down by")
    p.add_argument("--tags-col", default="Tags", help="column holding a JSON object of tags (FOCUS)")
    p.add_argument("--tag-column", action="append", default=[], metavar="KEY=COLUMN",
                   help="map a tag key to its own column; repeatable")
    p.add_argument("--top", type=int, default=10, help="rows to show in the breakdown")
    p.add_argument("--max-untagged", type=float, default=None,
                   help="fail (exit 1) if untagged percentage of cost exceeds this")
    return p.parse_args()


def money(raw: str) -> Decimal:
    try:
        return Decimal(raw.strip() or "0")
    except InvalidOperation:
        return Decimal("0")


def main() -> int:
    a = parse_args()
    required = [k.strip().lower() for k in a.required.split(",") if k.strip()]
    mapped = {}
    for item in a.tag_column:
        if "=" not in item:
            print(f"error: --tag-column expects KEY=COLUMN, got {item!r}", file=sys.stderr)
            return 2
        k, col = item.split("=", 1)
        mapped[k.strip().lower()] = col.strip()

    total = Decimal("0")
    untagged = Decimal("0")
    missing_by_key: dict[str, Decimal] = defaultdict(Decimal)
    untagged_by_group: dict[str, Decimal] = defaultdict(Decimal)
    rows = 0

    try:
        fh = open(a.csv, newline="", encoding="utf-8-sig")
    except OSError as exc:
        print(f"error: {exc}", file=sys.stderr)
        return 2
    with fh:
        reader = csv.DictReader(fh)
        cols = set(reader.fieldnames or [])
        if a.cost_col not in cols:
            print(f"error: cost column {a.cost_col!r} not found; columns: {sorted(cols)[:15]}...", file=sys.stderr)
            return 2
        use_json = not mapped
        if use_json and a.tags_col not in cols:
            print(f"error: tags column {a.tags_col!r} not found; use --tag-column KEY=COLUMN", file=sys.stderr)
            return 2
        for row in reader:
            rows += 1
            cost = money(row.get(a.cost_col, ""))
            total += cost
            if use_json:
                try:
                    tags = {str(k).lower(): str(v) for k, v in json.loads(row.get(a.tags_col) or "{}").items()}
                except (json.JSONDecodeError, AttributeError):
                    tags = {}
            else:
                tags = {k: (row.get(col) or "") for k, col in mapped.items()}
            missing = [k for k in required if not tags.get(k, "").strip()]
            for k in missing:
                missing_by_key[k] += cost
            if missing:
                untagged += cost
                untagged_by_group[row.get(a.group_col) or "(none)"] += cost

    if rows == 0:
        print("error: no data rows", file=sys.stderr)
        return 2

    pct = float(untagged / total * 100) if total else 0.0
    print(f"rows: {rows}")
    print(f"total cost: {total:.2f}")
    print(f"cost missing at least one required tag: {untagged:.2f} ({pct:.1f}%)")
    print("\nmissing cost by tag key:")
    for k in required:
        share = float(missing_by_key[k] / total * 100) if total else 0.0
        print(f"  {k:<20} {missing_by_key[k]:>14.2f}  {share:5.1f}%")
    print(f"\ntop untagged by {a.group_col}:")
    for name, cost in sorted(untagged_by_group.items(), key=lambda kv: kv[1], reverse=True)[: a.top]:
        print(f"  {name[:40]:<40} {cost:>14.2f}")

    if a.max_untagged is not None and pct > a.max_untagged:
        print(f"\nFAIL: untagged share {pct:.1f}% exceeds {a.max_untagged}%")
        return 1
    print("\nOK")
    return 0


if __name__ == "__main__":
    sys.exit(main())

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem