---
name: aws-cloud-cost-per-deploy
description: "Every morning: any service whose daily cloud cost stepped up, by how much a day, and the merged PR and deploy that most likely caused it. Use when the user asks for this, or when it runs on a schedule."
---

# Cloud cost changes matched to deploys

Every morning: any service whose daily cloud cost stepped up, by how much a day, and the merged PR and deploy that most likely caused it.

Cost Explorer tells you Lambda went from $38 to $64 a day. GitHub tells you what merged on Tuesday. Nobody puts the two on one clock, so the new cron job that runs every minute instead of every hour shows up on the invoice three weeks later, when the PR is long forgotten.

This skill is for the founder or platform lead who pays the cloud bill and also reviews the PRs. Every morning the agent reads daily cost per service from whichever clouds you use, finds the services whose cost stepped up and stayed up, and lines each step up with the PRs merged and deployed in the 48 hours before it. The card names the most likely PR, the files that point to it and the dollars per day it added.

## Two ways to get the data

Use the API or MCP route when you have an MCP server, a CLI or an API token for the app. Otherwise, if you can drive a browser the user is signed in to (Claude in Chrome, a Playwright or Chrome DevTools MCP server), use the browser route. Say which one you used.

## Route A: API or MCP server

### What you need

- Billing read access: an AWS role allowed `ce:GetCostAndUsage` (or `AWSBillingReadOnlyAccess`), a Vercel token for a team role that can see billing, a Cloudflare token with Billing Read, and a Supabase access token for the organization, only for the clouds you use.
- Read access to the repository through `gh`, the GitHub MCP server or a fine-grained token with Contents, Pull requests and Deployments: Read.
- Production deploys tied to a commit SHA (GitHub Deployments, the Vercel Git integration or a deploy workflow): that SHA is the key that joins a PR to the day its code started costing money.

If something is missing, say what and stop; do not invent numbers.

### Steps

1. **Pull 21 days of cost by service** (AWS): Call Cost Explorer `GetCostAndUsage` with `TimePeriod` covering the last 21 days (`End` is exclusive, so set it to today), `Granularity: DAILY`, `Metrics: ["UnblendedCost"]` and `GroupBy: [{ Type: "DIMENSION", Key: "SERVICE" }]`. That is one request ($0.01). Days are UTC and today is left out; mark yesterday as partial, because its cost can still grow after the first refresh.
2. **Add the other clouds you pay for** (Vercel): Whichever you use: Vercel `GET /v1/billing/charges?teamId=<team>&from=<ISO>&to=<ISO>` streams FOCUS rows as JSONL; sum `BilledCost` by `ServiceName` and the date of `ChargePeriodStart`, and keep `Tags.ProjectName` for the project. Cloudflare `GET /accounts/{account_id}/billable-usage` (an alpha endpoint with the same FOCUS field names; its range must include the billing anchor day). Supabase has no spend API, so read `GET /v1/projects/{ref}/billing/addons` for compute size changes and `usage.api-requests-count` as a usage trend, and label those rows usage, not dollars.
3. **Find the steps, not the spikes**: Per cloud and service, the baseline is the median daily cost of the 14 full days before the last 7. A step starts on the first day D in the last 7 full days from which every full day is at least $10 and 20% above the baseline, for at least 2 days in a row. One-day spikes are listed apart. Step size is the mean of D through yesterday minus the baseline, in dollars a day.
4. **Line up merges and deploys with each step** (GitHub): For each step, list PRs merged from 48 hours before D starts (00:00 UTC) to the end of D: `gh pr list --repo <repo> --state merged --search "merged:<D-2>..<D>" --json number,title,mergedAt,files,url,mergeCommit`. Keep only PRs whose `mergeCommit.oid` reached production in that window, matched on the commit SHA to `GET /repos/{owner}/{repo}/deployments?environment=production` or to Vercel deployments' `meta.githubCommitSha`. A merged PR that never deployed cannot have moved the bill.
5. **Name the most likely PR**: Score each candidate by its files: +3 when a file matches the service that stepped (Lambda or Functions: handlers and `api/`; RDS or Supabase: `migrations/`, `*.sql`, query code; CloudWatch: logging config; S3 or egress: upload and image code), +2 for infra or schedules (`*.tf`, CDK stacks, `serverless.yml`, `vercel.json` `crons`, `wrangler.toml` `[triggers]`, `.github/workflows`), +1 for the deploy closest to D. Top score wins; a step where no PR scores above 1 is reported as "no code change found" (console edits, traffic, a price change).
6. **Compare with yesterday and post**: Read the previous card first with `GET https://briefkin.dev/v1/cards/insight-aws-cloud-cost-per-deploy` (same `item_key` every run) so a step already reported shows as "still on, day 6" instead of new. Post the new and ongoing steps with dollars a day, the PR, the deploy time and the files that gave it away.

## Route B: the browser

### What you need

- Signed in to the AWS console with billing access (Cost Explorer enabled), to Vercel, Cloudflare or Supabase with a role that can see usage, and to GitHub with read access to the repository.

If something is missing, say what and stop; do not invent numbers.

### Steps

Start at https://us-east-1.console.aws.amazon.com/costmanagement/home#/cost-explorer. The user is signed in already; never type passwords or codes, and ask if a sign-in page appears.

1. **Read daily AWS cost by service**: In Cost Explorer, set the date range to the last 21 days, Granularity to `Daily` and Group by to `Service`. Read the table under the chart, or use Download as CSV, and treat the last day as partial.
2. **Read the other clouds you use**: Vercel: open the team, then the **Usage** tab, and read cost per product for the same days. Cloudflare: Manage Account > Billing and its billable usage page. Supabase: `https://supabase.com/dashboard/org/_/usage` for the organization. Where a page shows usage instead of dollars, compare the usage line and say so.
3. **Find the steps**: For each service, compare the last 7 full days with the median of the 14 days before. A step is at least 2 full days in a row, each at least $10 and 20% above that median; note the first day of the step.
4. **Open the PRs merged before each step**: On GitHub open `https://github.com/<owner>/<repo>/pulls?q=is%3Apr+is%3Amerged+merged%3A<D-2>..<D>` with the step day as D. For each PR, open **Files changed** and check the repository's **Deployments** page (or the Vercel project's Deployments, filtered to Production) for when it went live.
5. **Name the likely PR**: Prefer a PR that touched files for the service that stepped (handlers for Lambda, migrations and queries for the database, logging config for log costs) or schedules and infra (`serverless.yml`, `vercel.json` crons, Terraform). Report dollars a day, the PR and its files, and any step with no matching change.

Note: Vercel, Cloudflare and Supabase usage pages chart usage by product but do not all give a dollar figure per day, so the browser route may compare usage instead of cost for those. Cost Explorer's console and API return the same numbers. Checking Files changed for every PR is slow past about 20 merged PRs a day; use the API route then.

## The report

- **Daily cost change**: total dollars a day added since the last steady baseline, across clouds.
- **Each step**: cloud, service, cost a day before and after, the day it started.
- **The likely cause**: PR number and title, when it merged and deployed, and the files that point to it.
- **Unexplained steps**: cost rises with no matching change in code.
- The first thing to do, for example "Change the cron in PR 88 from every minute to every 15 minutes".

## Post it to Briefkin

When `BRIEFKIN_TOKEN` is set, or `~/.config/briefkin/env` exists (the installer from https://briefkin.dev/docs writes it), also send the result to the user's Briefkin board as one card. Keep the `item_key` the same on every run so the card updates in place, use a new `event_id` per run, and put today's real numbers in place of the example ones. If the request fails, say so once and carry on.

```bash
[ -n "${BRIEFKIN_TOKEN:-}" ] || . ~/.config/briefkin/env
curl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
  -H "authorization: Bearer $BRIEFKIN_TOKEN" \
  -H "content-type: application/json" \
  -d @- <<'JSON'
{
  "v": 1,
  "event_id": "aws-cloud-cost-per-deploy/<date of this run, e.g. 2026-10-16>",
  "item_key": "aws-cloud-cost-per-deploy",
  "project": "acme-web",
  "source": "Cloud cost changes matched to deploys skill",
  "kind": "update",
  "title": "Cost up $41 a day since PR 88 (the new cron) on Tuesday",
  "summary": "Daily cloud cost went from **$182 to $223** after PR 88 deployed on Tuesday Oct 20 at 16:40 UTC. It added a scheduled Lambda every minute, and both Lambda and CloudWatch Logs stepped the next day.",
  "widget": [
    {
      "type": "Table",
      "props": {
        "columns": [
          "Service",
          "Before",
          "Now",
          "Since",
          "Likely cause"
        ],
        "align": [
          "left",
          "right",
          "right",
          "left",
          "left"
        ],
        "rows": [
          [
            "AWS Lambda",
            "$38/day",
            "$64/day",
            "Oct 21",
            "PR 88 add sync-inventory cron"
          ],
          [
            "AWS CloudWatch Logs",
            "$9/day",
            "$24/day",
            "Oct 21",
            "PR 88 (debug logging in the handler)"
          ],
          [
            "Vercel Function Duration",
            "$21/day",
            "$21/day",
            "steady",
            "none"
          ]
        ]
      }
    },
    {
      "type": "Sparkline",
      "props": {
        "label": "Daily cost, all clouds",
        "prefix": "$",
        "better": "lower",
        "values": [
          181,
          183,
          180,
          184,
          182,
          179,
          183,
          182,
          181,
          184,
          182,
          180,
          183,
          182,
          222,
          224,
          223,
          221,
          224,
          223
        ]
      }
    },
    {
      "type": "KeyValue",
      "props": {
        "items": [
          {
            "key": "PR 88 files",
            "value": "serverless.yml (schedule: rate(1 minute)), src/jobs/syncInventory.ts"
          },
          {
            "key": "Deployed",
            "value": "Oct 20, 16:40 UTC, commit 4e1c9a2"
          },
          {
            "key": "Month-end effect",
            "value": "About $450 more this month at the current rate"
          }
        ]
      }
    },
    {
      "type": "Callout",
      "props": {
        "text": "Change the schedule in PR 88 from rate(1 minute) to rate(15 minutes) and drop the debug log line: that brings back most of the $41.",
        "tone": "warn"
      }
    }
  ]
}
JSON
```

Widgets come from the Briefkin catalog (https://briefkin.dev/v1/catalog). Without a token, just give the user the report.

## Run it on a schedule

Suggested: Every day at 8:00 (`0 8 * * *`). From cron, with the agent signed in on that machine:

```bash
0 8 * * * claude -p "Run the aws-cloud-cost-per-deploy skill"
# Codex: codex exec, Gemini CLI: gemini -p, Copilot CLI: copilot -p, OpenCode: opencode run
```

Or use the agent's own scheduler (Claude Code routines, Codex automations, Claude or ChatGPT scheduled tasks): https://briefkin.dev/skills/aws-cloud-cost-per-deploy#schedule

Source: https://briefkin.dev/skills/aws-cloud-cost-per-deploy
