AWS Cost Optimization skill for Codex
Every saving traced to a line on the bill. Codex starts from the bill, drilling from service to usage type until the meter behind the spend is obvious, then sweeps for orphaned volumes, idle IPv4, endless log retention and NAT traffic that a free endpoint would carry. Findings come back ranked by monthly dollars and risk, nothing is deleted or purchased without approval, and commitments are reviewed only after waste is removed.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- aws-cost-optimization Installs to ~/.agents/skills/aws-cost-optimization/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install aws-cost-optimization The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project aws-cost-optimization Installs to .agents/skills/aws-cost-optimization/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/aws-cost-optimization.zipunzip -o aws-cost-optimization.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) aws-cost-optimization PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $aws-cost-optimization, 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
Finds and cuts AWS spend from billing data - Cost Explorer and CUR 2.0 breakdowns by service and usage type, idle and orphaned resources (unattached EBS, idle load balancers, unused Elastic IPs, old snapshots), NAT gateway and data transfer charges, CloudWatch Logs retention, gp2 to gp3, Graviton, right-sizing with Compute Optimizer, and Savings Plans coverage. Use when the AWS bill jumped, someone asks where the money goes, a monthly cost review is due, or before buying Savings Plans or Reserved Instances.
Use it when
- The AWS bill jumped and nobody knows why
- A monthly or quarterly cost review is due
- Before buying Savings Plans or Reserved Instances
- NAT gateway, data transfer or CloudWatch Logs charges look high
Use something else when
- You need tagging, budgets and cost governance across several clouds: use cloud-finops
- The question is purely S3 storage design: use aws-s3
How to check the result
Each finding cites the Cost Explorer usage type or CLI output behind it, and Cost Explorer shows the expected drop in that usage type a week after approved changes ship.
The mistake it prevents
Buying Savings Plans before removing idle and oversized resources, which commits to paying for the waste for one to three years.
Done well means: every recommendation traces to a line in the bill, savings are estimated in dollars per month with the assumption shown, the cheap safe wins ship first, nothing is deleted or purchased without explicit approval, and the change is visible in Cost Explorer the following week.
Hard rules
- Analysis is read-only. Deleting resources, stopping instances, changing storage classes, and buying Savings Plans or Reserved Instances all require the user’s explicit confirmation after they have seen the list and the estimate.
- Commitment purchases are a business decision with a 1- or 3-year term. Present coverage, utilization and break-even; the user (or their finance owner) decides. Do not frame it as financial advice.
- Quote prices as “us-east-1 list price at time of writing” and tell the user to confirm current regional pricing; prices change and vary by region.
- Cost Explorer API calls are billed per request (about $0.01 each). Batch queries rather than looping per day per service.
Workflow
- Get the shape of the bill: top services, last 3 months, monthly
- Drill into the top 3 services by usage type to find the real driver
- Run the quick-win sweep (orphans and defaults)
- Check architecture-level drivers (NAT, data transfer, logs, over-provisioning)
- Check commitment coverage last, after waste is removed
- Present findings ranked by monthly savings and risk; get approval per action
- Apply approved changes through IaC where the resource is managed by IaC
- Re-check Cost Explorer after 7 days
1. Shape of the bill (needs network and credentials)
aws ce get-cost-and-usage \
--time-period Start=2026-07-01,End=2026-10-01 --granularity MONTHLY \
--metrics UnblendedCost --group-by Type=DIMENSION,Key=SERVICE \
--output json > cost-by-service.json
# Amounts are strings; sort them in Python or jq, not in a JMESPath filter
python3 -c "import json;d=json.load(open('cost-by-service.json'))
for m in d['ResultsByTime']:
rows=sorted(((float(g['Metrics']['UnblendedCost']['Amount']),g['Keys'][0]) for g in m['Groups']),reverse=True)
print(m['TimePeriod']['Start'],*[f'{s}: {a:,.0f}' for a,s in rows[:8]],sep='\n ')"
# Drill into one service by usage type, the dimension that names the actual meter
aws ce get-cost-and-usage \
--time-period Start=2026-09-01,End=2026-10-01 --granularity MONTHLY \
--metrics UnblendedCost \
--filter '{"Dimensions":{"Key":"SERVICE","Values":["Amazon Elastic Compute Cloud - Compute"]}}' \
--group-by Type=DIMENSION,Key=USAGE_TYPE
Usage types tell the story: NatGateway-Bytes (NAT processing), DataTransfer-Regional-Bytes
(cross-AZ), TimedStorage-ByteHrs (S3 storage), EBS:VolumeUsage.gp2, PublicIPv4:InUseAddress,
DataProcessing-Bytes under CloudWatch (log ingestion).
For organizations with many accounts, group by LINKED_ACCOUNT first. For line-item
analysis at scale, query a CUR 2.0 Data Export with Athena instead of Cost Explorer.
If the user exported a CSV from the billing console, analyse it locally with Python or DuckDB; that needs no network or credentials.
2. Quick-win sweep
Run per region (--region), since most of these are regional.
# Unattached EBS volumes
aws ec2 describe-volumes --filters Name=status,Values=available \
--query 'Volumes[].[VolumeId,Size,VolumeType,CreateTime]' --output table
# gp2 volumes to migrate to gp3 (cheaper per GB, baseline 3,000 IOPS, online change)
aws ec2 describe-volumes --filters Name=volume-type,Values=gp2 --query 'length(Volumes)'
# Elastic IPs not associated (all public IPv4 addresses are billed hourly)
aws ec2 describe-addresses --query 'Addresses[?AssociationId==null].[PublicIp,AllocationId]'
# Snapshots owned by this account, oldest first
aws ec2 describe-snapshots --owner-ids self \
--query 'sort_by(Snapshots,&StartTime)[:30].[SnapshotId,VolumeSize,StartTime,Description]'
# Log groups that never expire
aws logs describe-log-groups --query 'logGroups[?retentionInDays==null].[logGroupName,storedBytes]'
# Load balancers: check RequestCount / ActiveConnectionCount in CloudWatch for zero traffic
aws elbv2 describe-load-balancers --query 'LoadBalancers[].[LoadBalancerName,Type,CreatedTime]'
Before proposing a deletion, check tags, IaC ownership and whether a snapshot is the only backup of something. Prefer “snapshot, then delete” for volumes, and list the exact IDs in the approval request.
3. Architecture-level drivers
| Finding | Typical fix |
|---|---|
High NatGateway-Bytes | S3 and DynamoDB gateway VPC endpoints (free); interface endpoints for ECR, Logs, STS when traffic justifies their hourly cost |
| High cross-AZ transfer | Keep chatty services and their caches in the same AZ where safe; check load balancer cross-zone settings; avoid Multi-AZ for dev |
| CloudWatch Logs ingestion | Lower log level, drop health-check logs, sample debug logs, set retention; Infrequent Access log class for logs you rarely query |
| Instances under 20% CPU for weeks | Right-size from Compute Optimizer; move to Graviton (m7g, c7g, r7g and newer) when the stack supports arm64 |
| Non-production running 24/7 | Schedule stop outside working hours (Instance Scheduler, EventBridge Scheduler) |
| RDS on old engine version | Upgrade before Extended Support charges start |
| S3 growth | Lifecycle rules, Intelligent-Tiering, abort incomplete multipart uploads, expire noncurrent versions |
| Lambda cost | Right-size memory, arm64, cut init time |
| EKS/ECS idle capacity | Right-size requests, Spot for interruptible workloads |
Deep checklists per service, with the commands and the trade-off for each fix, are in references/service-checklists.md. Read the section for a service only when it is in the top five of the bill.
4. Commitments, last
Remove waste first; committing to wasted capacity locks the waste in for years.
aws ce get-savings-plans-coverage --time-period Start=2026-09-01,End=2026-10-01 --granularity MONTHLY
aws ce get-savings-plans-utilization --time-period Start=2026-09-01,End=2026-10-01
aws ce get-savings-plans-purchase-recommendation \
--savings-plans-type COMPUTE_SP --term-in-years ONE_YEAR \
--payment-option NO_UPFRONT --lookback-period-in-days THIRTY_DAYS
| Commitment | Covers | Flexibility |
|---|---|---|
| Compute Savings Plan | EC2, Fargate, Lambda, any region, family, OS | Highest; good default |
| EC2 Instance Savings Plan | One instance family in one region | Bigger discount, less flexible |
| Reserved Instances | RDS, ElastiCache, OpenSearch, Redshift | Required for these services |
Present: current on-demand baseline (the steady floor, not the average), proposed hourly commitment, expected utilization, estimated savings, and the risk if usage drops. Leave the purchase decision and the click to the user.
Prevention
- Cost allocation tags (
team,service,env) activated in the billing console; enforce tagging in IaC. - AWS Budgets with alerts at 80% and 100% of forecast per account or team.
- Cost Anomaly Detection monitors on services and linked accounts.
- A monthly review using this workflow, with savings tracked against the previous month.
Gotchas
- Unblended vs amortized cost: with upfront commitments, amortized shows the real monthly cost. Use the same metric when comparing months.
- Credits and refunds hide real usage. Filter
RECORD_TYPEtoUsagewhen analysing. - Stopping an EC2 instance still bills its EBS volumes and Elastic IPs.
- Deleting a snapshot that an AMI references fails; deregistering the AMI first is a destructive step that needs confirmation.
- Moving data out of Glacier or IA early incurs minimum-duration and retrieval charges.
- A savings estimate from a single month can be skewed by one-off events; check 3 months.
Output
## AWS cost review: <account or org>, <period>
**Total:** $X/month (trend: +Y% over 3 months). Top drivers: <service: $, usage type>
| # | Finding | Evidence | Action | Est. saving/month | Risk | Needs approval |
|---|---|---|---|---|---|---|
| 1 | 14 unattached gp2 volumes, 3.2 TB | describe-volumes, us-east-1 | Snapshot then delete | ~$260 | Low | Yes |
**Commitments:** coverage X%, utilization Y%; recommendation summary for the user to decide.
**Not checked:** <regions, accounts, services skipped>
**Prices:** us-east-1 list prices at time of writing; confirm current pricing. 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.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "AWS Cost Optimization"
short_description: "Every saving traced to a line on the bill"
default_prompt: "$aws-cost-optimization Our AWS bill went up 40% last month, find out why" references/service-checklists.md
# Per-service cost checklists
Read only the section for a service that is in the top five of the bill. Every action that
deletes, stops, resizes or purchases needs explicit user approval. Prices mentioned are
approximate us-east-1 list prices; confirm current regional pricing before quoting savings.
## EC2 and EBS
| Check | How | Fix and trade-off |
|---|---|---|
| Low utilization | Compute Optimizer: `aws compute-optimizer get-ec2-instance-recommendations` (must be opted in) | Downsize one step at a time; watch p99 latency and memory, which CloudWatch does not collect without the agent |
| x86 on a portable stack | Instance family list | Graviton equivalents are usually cheaper per hour; rebuild images for arm64 and load-test |
| Previous-generation families (`m4`, `c4`, `t2`) | describe-instances | Current generation is cheaper and faster; requires a stop/start |
| gp2 volumes | `describe-volumes --filters Name=volume-type,Values=gp2` | `aws ec2 modify-volume --volume-id vol-x --volume-type gp3` is online; gp3 includes 3,000 IOPS and 125 MB/s; gp2 volumes over 1 TB have a higher baseline (3 IOPS per GB), so provision matching gp3 IOPS and throughput for them |
| io1/io2 with low IOPS use | CloudWatch `VolumeReadOps`/`VolumeWriteOps` | gp3 with provisioned IOPS up to its limit |
| Unattached volumes | status `available` | Snapshot, then delete after confirmation |
| Old snapshots and AMIs | `describe-snapshots --owner-ids self`, `describe-images --owners self` | Data Lifecycle Manager policies; snapshot archive tier for long retention |
| Spot candidates | Batch, CI runners, stateless workers | Spot with diversified instance types and capacity-optimized allocation |
## Networking
| Check | How | Fix |
|---|---|---|
| NAT gateway processing | Usage type `NatGateway-Bytes`; VPC Flow Logs to find top destinations | Gateway endpoints for S3 and DynamoDB (no charge); interface endpoints for ECR, CloudWatch Logs, STS, Secrets Manager when the per-GB saving beats the hourly endpoint cost per AZ |
| One NAT gateway per AZ in dev | describe-nat-gateways | A single NAT for non-production (accepting AZ failure risk) |
| Cross-AZ transfer | Usage type `DataTransfer-Regional-Bytes` | Co-locate chatty services, use AZ-aware routing, avoid cross-AZ replicas in dev |
| Internet egress | `DataTransfer-Out-Bytes` | Serve static and cacheable content via CloudFront; compress responses |
| Idle public IPv4 | `PublicIPv4:IdleAddress`, `InUseAddress` | Release unused Elastic IPs; private subnets behind load balancers; IPv6 where clients support it |
| Idle load balancers | CloudWatch `RequestCount` (ALB) or `ActiveFlowCount` (NLB) near zero for 14 days | Delete after confirmation; consolidate many small ALBs with host-based routing |
## S3
| Check | How | Fix |
|---|---|---|
| Storage by class and bucket | S3 Storage Lens dashboard | Lifecycle transitions; Intelligent-Tiering for unknown access patterns |
| Incomplete multipart uploads | Storage Lens metric | Lifecycle `AbortIncompleteMultipartUpload` after 7 days |
| Noncurrent versions | Storage Lens, versioned buckets | `NoncurrentVersionExpiration` with `NewerNoncurrentVersions` |
| Request-heavy workloads | Usage types `Requests-Tier1`/`Tier2` | Batch small objects, cache in front, avoid LIST-heavy polling |
| Tiny objects in IA/Glacier | Average object size | Minimum billable size and duration make tiering tiny objects counterproductive |
## RDS and Aurora
| Check | How | Fix |
|---|---|---|
| Extended Support | Engine version vs end of standard support | Upgrade the major version |
| Oversized instances | `CPUUtilization`, `FreeableMemory` over 30 days | Downsize; Graviton classes (`r7g`, `m7g`) |
| Aurora I/O cost | Billing: `StorageIOUsage` | Aurora I/O-Optimized when I/O is above roughly 25% of cluster cost |
| Idle dev databases | Connections near zero | Aurora Serverless v2 with min 0 ACU and auto-pause, or stop RDS instances (they restart automatically after 7 days) |
| Manual snapshots kept forever | describe-db-snapshots `--snapshot-type manual` | Retention policy, confirm before deleting |
| Reserved Instances | Coverage report | RIs for steady production after right-sizing |
## Lambda
| Check | How | Fix |
|---|---|---|
| Memory over-provisioned | `Max Memory Used` vs configured in REPORT logs | Power-tune; lower memory only when duration does not rise proportionally |
| x86_64 | Function config | arm64 is cheaper per GB-second |
| Long init | `Init Duration` | Init is billed; trim imports and bundle size |
| Provisioned concurrency always on | Config and utilization metric | Schedule it to business hours or drop it |
| Polling loops | Invocation count vs useful work | Event-driven triggers instead of scheduled polling |
## Containers (ECS, EKS)
| Check | How | Fix |
|---|---|---|
| Task/pod requests far above usage | Container Insights | Right-size CPU/memory; for Fargate use the smallest valid pair that fits p95 |
| Always-on non-production | Desired count | Scale to zero outside hours with scheduled scaling |
| Interruptible work on on-demand | Workload type | Fargate Spot or EC2 Spot node groups (Karpenter) |
| Idle EKS nodes | Node utilization | Karpenter consolidation, smaller nodes, fewer node groups |
## CloudWatch
| Check | How | Fix |
|---|---|---|
| Log ingestion | Usage type `DataProcessing-Bytes`; `IncomingBytes` per log group | Lower verbosity, drop noisy lines at source, sample debug |
| Storage | Log groups with no retention | Set retention (14-90 days typical) and export long-term logs to S3 |
| Custom metrics | Count of metrics with high-cardinality dimensions | Remove per-request or per-user dimensions; use EMF with care |
| Detailed monitoring and dashboards | Billing line items | Disable 1-minute EC2 metrics where unused |
## DynamoDB
| Check | How | Fix |
|---|---|---|
| Provisioned capacity unused | `ConsumedReadCapacityUnits` vs provisioned | Auto scaling or on-demand for spiky tables |
| On-demand at steady high load | Consumption pattern | Provisioned with auto scaling and reserved capacity |
| Rarely read tables | Access pattern | Standard-IA table class |
| Large items and scans | Item size, Scan usage | Projections, GSIs with sparse keys, avoid Scan in hot paths |
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.