Blog

Notes on building small tools

2026-08-29

We found two deploy bugs in a row by testing the deploy paths we never run

Our default deploy shipped a Worker bound to an empty D1 database for weeks — migrations were applying to local, not remote. Fixing that surfaced a second bug one layer down: wrangler's named environments don't inherit top-level bindings, so --env staging and --env production were broken too.

2026-08-28

Fixing a race in one function doesn't fix the same race in its neighbor

We fixed SnapOG's quota-increment race weeks ago. The sibling function that resets the quota every billing month had the exact same shape and was never touched — found by writing the pattern down as a checklist and sweeping the whole codebase with it, not by hunting for one more instance by hand.

2026-08-28

Your rate limit isn't a rate limit if two requests can both slip through

A per-tier interval floor read a timestamp, computed elapsed time in JS, then wrote unconditionally. Ten concurrent pings for the same check all read the same stale timestamp and all passed — the fifth instance of the same race shape, this time in a cost-control rate limit.

2026-08-28

Your PATCH endpoint can silently drop half of a concurrent edit

Two requests patch different fields of the same row, both merging against the same stale snapshot. Whichever UPDATE lands second wins completely — the other edit just vanishes, no error. Why the obvious updated_at guard doesn't work, and the one that does.

2026-08-28

Your quota check has a race condition even after you "fixed" it once

An in-memory usage check, an image render, an R2 write, then an unconditional counter increment. The gap wasn't two database statements — it was real I/O. Why fixing this bug class once elsewhere didn't catch it here.

2026-08-28

We fixed four bugs. None of them reached our own public repo.

Every fix was tested and written up, but the public GitHub repos self-hosters actually deploy from were frozen at initial release the whole time. How that happened, and the check that closes it.

2026-08-28

Your recovery webhook can fire twice for one incident

A monitored service retrying a timed-out heartbeat sends two near-simultaneous recovery pings. Without a guard, both read the same pre-transition status and both page the customer. The one-line UPDATE guard that closes it — the same bug family, a third instance.

2026-08-28

Your content-addressed cache has no eviction policy. It will grow forever.

Hashing request params into an R2 object key makes a great cache — but nothing ever deletes the old entries. The daily sweep that fixes it, mirrored from a pattern already built elsewhere in the same codebase.

2026-08-28

Your "check quota then insert" tier limit has a race condition

SELECT COUNT(*) followed by a separate INSERT let 10 concurrent requests blow past a 25-check limit. The one-statement fix, and why it applies to any SQLite/D1-backed quota.

2026-08-28

Why your Satori-based OG images have overlapping text

An old pinned Satori/workers-og version rendered every two-line title as a stacked, unreadable mess. The cause, the one-line fix, and a pixel-diffing test that catches it for good.

2026-08-28

Your webhook_url field is an SSRF hole. Here's how we closed it.

Any feature that fetches a URL a user gave you can be turned into a proxy into your own network. The checks that actually close the hole, and the one gap (DNS rebinding) that's still open.

2026-08-28

Cron jobs fail silently. Here's how to catch it in 5 minutes.

Your job doesn't crash when it stops running — it just stops. The dead man's switch pattern, and a free, self-hosted way to catch it on your own Cloudflare account.

2026-08-28

How to add dynamic OG images to any site in 5 minutes

Every link you share is a first impression. Here's the fastest path from "no og:image tag" to a real one — including a free, self-hosted option you can deploy to your own Cloudflare account right now.