Cost up $41 a day since PR 88 (the new cron) on Tuesday
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.
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.
Reads
You get
The card on the right is a sample. Yours shows your own numbers and updates in place every run.
What lands on your board
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.
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.
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.
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.
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.
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.
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).
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.
With BRIEFKIN_TOKEN set, the agent sends the result as one card that updates in place every run. Without it, you get the report in the chat.
Pick your agent, where the data comes from and how to schedule it; the commands below follow.
One command. It puts the SKILL.md in ~/.claude/skills and in ~/.agents/skills, which Codex, Cursor, Gemini CLI, Copilot CLI and OpenCode read too.
shellcurl -fsSL https://briefkin.dev/skills/aws-cloud-cost-per-deploy/install | shAsk Claude Code:
promptRun the aws-cloud-cost-per-deploy skillOr paste the prompt instead of installing:
promptPull the last 21 days of daily cost by service from AWS Cost Explorer (GetCostAndUsage, DAILY, UnblendedCost, group by SERVICE) and from Vercel, Cloudflare or Supabase if I use them. For each service, compare the last 7 full days with the median of the 14 days before. A step is a run of at least 2 full days, each at least $10 and 20% above that median. For each step, list the PRs in <owner/repo> merged from 48 hours before the step day through that day, keep the ones whose merge commit reached production, and pick the most likely one by the files it touched (infra, cron schedules, queries, logging). Tell me the dollars a day each step added, the PR and the files, any step with no matching PR, and the one change I should make first.Every day at 8:00, from cron on any machine where Claude Code is signed in:
crontab0 8 * * * claude -p "Run the aws-cloud-cost-per-deploy skill"Add it with crontab -e. A laptop asleep at that time runs it when it wakes if you use launchd or Task Scheduler instead; GitHub Actions works for the API route.
Connect Briefkin once with curl -fsSL https://briefkin.dev/install | sh and every run lands on your board as a report card. How setup works
SKILL.md---
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
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.
https://aws-mcp.us-east-1.api.aws/mcp (other Regions follow the same pattern, such as aws-mcp.eu-west-1.api.aws). In Claude Code, run claude mcp add aws-mcp https://aws-mcp.us-east-1.api.aws/mcp --transport http and sign in through AWS Sign-in; your IAM user or role needs the AWSMCPSignInOAuthAccessPolicy managed policy for that step. Cursor takes the URL with ?oauth=initialize appended, and Codex uses codex mcp add aws-mcp --url https://aws-mcp.us-east-1.api.aws/mcp?oauth=initialize (setup).aws login or an SSO profile, run it as uvx mcp-proxy-for-aws-cli@latest https://aws-mcp.us-east-1.api.aws/mcp --read-only. The proxy signs each request with SigV4, and --read-only hides the tools that may need write permissions. AWS also publishes an open-source Billing and Cost Management MCP server (uvx awslabs.billing-cost-management-mcp-server@latest) focused on Cost Explorer, budgets, anomalies and Savings Plans.aws ce get-cost-and-usage --time-period Start=2026-10-01,End=2026-10-16 --granularity DAILY --metrics UnblendedCost --group-by Type=DIMENSION,Key=SERVICE returns the same data. The skill reads AWS_PROFILE and AWS_REGION; Cost Explorer itself is served from us-east-1.Run reports under a role that can only read billing data, for example one allowed ce:GetCostAndUsage, ce:GetCostForecast, ce:GetAnomalies and budgets:ViewBudget, or the AWSBillingReadOnlyAccess managed policy: the MCP server can do no more than that role allows.
https://mcp.vercel.com, signed in with OAuth. It lists projects and deployments, reads build and runtime logs, and searches Vercel's docs.vercel CLI. vercel ls <project> lists recent deployments, vercel inspect <deployment-url> --logs prints the build log, and vercel logs <deployment-url> shows runtime logs. Pass --token $VERCEL_TOKEN and --scope <team> in scheduled runs.VERCEL_TOKEN, with VERCEL_TEAM_ID sent as teamId.Vercel tokens can be limited to one team and given an expiry but can't be made read-only, so use a short-lived token for reports and keep deploy and env var tasks out of the same skill.
https://mcp.cloudflare.com/mcp. In Claude Code run claude mcp add --transport http cloudflare https://mcp.cloudflare.com/mcp, in Codex codex mcp add cloudflare --url https://mcp.cloudflare.com/mcp, and in Cursor add the URL to mcp.json; you then authorize through Cloudflare and choose the permissions to grant. It reaches the whole API through two tools, search to find endpoints and execute to call them (GraphQL analytics queries included), plus a docs tool (docs). The older per-product servers, such as Observability and GraphQL, are deprecated in its favor.Authorization: Bearer. An account token also needs Account Resources: Read so the server can find the account ID.CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID (the names Wrangler uses too) and posts GraphQL queries; wrangler tail <worker> streams live logs when you need to watch an error happen.Create the token with only Account Analytics: Read, Zone Analytics: Read and Workers Scripts: Read on the accounts and zones in the report; with no Edit permissions, execute can't change anything.
https://mcp.supabase.com/mcp, signed in with OAuth. URL options narrow it: project_ref=<ref> limits it to one project, read_only=true runs every query as a read-only Postgres user, and features=database,debugging,docs loads only those tool groups. In Claude Code: claude mcp add --scope project --transport http supabase "https://mcp.supabase.com/mcp?project_ref=<ref>&read_only=true", then /mcp and Authenticate. In Codex, add the URL under [mcp_servers.supabase] in ~/.codex/config.toml and run codex mcp login supabase; in Cursor, put it under mcpServers in .cursor/mcp.json. Supabase is also in Claude's connector directory (docs).SUPABASE_ACCESS_TOKEN, and send it as Authorization: Bearer to the same project-scoped, read-only URL.DATABASE_URL, or run supabase link --project-ref <ref> and then the supabase inspect db commands.Supabase's own advice for unattended routines: scope the server to one project, set read_only=true, limit features, and keep production out of reach unless the report needs it.
https://api.githubcopilot.com/mcp/. Sign in with OAuth or a personal access token; add /readonly to the URL, or use a toolset URL such as https://api.githubcopilot.com/mcp/x/pull_requests/readonly, to expose only read tools. It also runs locally (source).gh CLI. Run gh auth login once, or set GH_TOKEN for scheduled runs. gh pr list --search "review-requested:@me" --json number,title,url,createdAt and gh api repos/{owner}/{repo}/dependabot/alerts cover most reports.GITHUB_TOKEN, limit it to the repositories in the report, and grant Read on Metadata, Contents, Pull requests and Issues, plus Dependabot alerts if you report on them.With every permission on a fine-grained token set to Read, the agent can't push, merge, comment or close anything.
The card says so plainly and leaves the cause open. Common reasons are changes made in a cloud console, a traffic jump, a new customer importing data, or a price change. For AWS, Cost Anomaly Detection (GetAnomalies) often names the usage type behind it, which narrows the search.
UTC for everything. Cost Explorer days and Vercel's ChargePeriodStart are UTC days, and GitHub's mergedAt and deployment timestamps are UTC too. Because costs come by the day, the skill looks 48 hours back from the start of the step day so a deploy late the evening before is not missed.
No. The skill runs with your own agent and gives you the report in the chat. With a Briefkin token set, each run also posts it to your board as a report card, so you can glance at it instead of asking again.
Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot CLI, OpenCode, Windsurf and Amp all read SKILL.md files. The install command puts it in ~/.claude/skills and ~/.agents/skills, where they look. ChatGPT, the Claude app and Grok take the prompt instead. Pick your agent under "Install and run it" and the commands change to match.
Yes. Switch the data source to "Your browser" and the steps change to the AWS, Vercel, Cloudflare, Supabase and GitHub pages themselves: the agent opens us-east-1.console.aws.amazon.com/costmanagement/home#/cost-explorer in a browser you are signed in to and reads the numbers off the page. Claude Code, Codex, Cursor, Claude app and ChatGPT can drive a browser out of the box; Gemini CLI, Copilot CLI and OpenCode need a Playwright or Chrome DevTools MCP server first. It is slower than the API and needs you signed in, so it suits a report you run by hand or from a scheduler on your own computer.
Skills are free to use with any agent. Briefkin is where their results land: one board for every report, number and question your agents send, with a ping when something needs you.
Free during the open beta. No card.