scram

scram

A reactor scram is the emergency full shutdown: drop every rod, stop the reaction, ask questions later.

Cloudflare has no hard spending limit. Its budget alerts say so explicitly — "The alert is informational only. It does not cap your usage or impact your account in any way" — and they are computed once a day from the previous day's usage, so the email arrives a day after the money is gone.

scram is the missing enforcement. It runs as a Worker in your own account, adds up what you have actually spent this billing cycle, and when you cross a line you set, it switches the account off.

What it does

Estimates in minutes, not a day.
Reads the GraphQL Analytics API every 15 minutes and prices it against Cloudflare's published rates.
Discovers everything.
Every Worker, route, custom domain, cron trigger and workers.dev subdomain in the account, on every run. Deploy a new project and it is covered immediately.
Reversible.
The state it took away is written down before anything is touched, and POST /api/restore puts it back.
Safe by default.
Ships disarmed. It will watch and report for as long as you want before you let it touch anything.
Cannot switch itself off.
scram excludes itself from every plan, because a scram that disables its own route cannot be restored through its own UI.
Costs almost nothing to run.
2,880 Worker invocations a month and a D1 row per check. Inside the free tier of the thing it is protecting you from.

What a trip actually does

For every Worker except the protected ones:

  1. Routes. Deletes each zone route pointing at it. Traffic stops reaching the Worker and the zone serves whatever it would without one.
  2. Custom domains. Deletes the Worker custom domain binding.
  3. Crons. Clears the schedule list, so nothing fires again.
  4. workers.dev. Disables the subdomain.

What it deliberately does not do: delete Workers, delete code, delete bindings, delete R2 buckets or objects, delete D1 databases or rows, cancel subscriptions, or downgrade plans. Every one of those is either irreversible or does not stop spend any faster than the four above.

Storage keeps accruing while tripped. R2 and D1 charge for bytes at rest, and nothing short of deleting your data stops that. scram will not delete your data. If you are tripping on storage rather than compute, it buys you time, not a cure.

Install

Needs Node 22+ and a Cloudflare account.

git clone https://github.com/pid1/scram && cd scram
npm install
npx wrangler login

./scripts/setup.sh      # database, migrations, secrets
npm run deploy

setup.sh prints the generated admin token at the end. Write it down. It is what opens the status page, and it is not recoverable afterwards.

Arming it

scram deploys disarmed, because ARMED is a secret and starts unset. In that state it collects usage, prices it, records a reading, and on crossing the threshold it writes a full trip record and notifies you, marked as a dry run, listing exactly what it would have disabled. Nothing changes.

Let it run for a few days. Compare the number on the status page against Manage Account → Billing → Billable Usage in the dashboard. When you believe it, arm it:

printf 1 | npx wrangler secret put ARMED

Disarm again at any time with npx wrangler secret delete ARMED. Neither needs a redeploy.

Configuration

VarDefaultMeaning
SCRAM_AT_USD20Estimated cycle spend that trips the switch
WARN_AT_USD5Sends a notification, changes nothing
BILLING_CYCLE_DAY1Day of month your cycle starts, per the dashboard
PROTECTemptyExtra scripts to never touch. scram is always included
ACTIONSall fourroutes, custom_domains, crons, subdomain
CF_ACCOUNT_IDemptyOnly needed if the token can see several accounts

Thresholds are measured against usage-based spend only. The API token scram needs, and why it needs each scope, is in the README. That token can disable every Worker in the account, which is the entire point of it, and the reason it lives only as a Worker secret.

Endpoints

GET /Status page
GET /healthcheckUnauthenticated, returns ok
GET /api/statusCurrent estimate, per-meter breakdown, policy, history
GET /api/previewEverything a trip would disable right now
POST /api/checkRun a check immediately
POST /api/scramTrip manually, ignoring the estimate. Honours ARMED
POST /api/restoreUndo the open trip

Everything under /api needs Authorization: Bearer <ADMIN_TOKEN>.

What it is not

Currently priced: Workers, D1, R2, Durable Objects, Vectorize, Queues. Rates were verified on 2026-09-19, and the date is shown on the status page so a stale table is visible rather than quietly wrong.

The number is an estimate. It is not your invoice, and it is not a guarantee.