
The Spare Key Everyone Already Has
An API that cannot find its signing key should not invent one. This week I found one that did, and it fell back to a key already sitting in the repo.
Topic
75 posts on this topic.

An API that cannot find its signing key should not invent one. This week I found one that did, and it fell back to a key already sitting in the repo.

A crew of AI agents fixed about eighty findings in one of my apps this week. The rule that made it work: an agent's report is testimony, not evidence.

I added a safety net to make failed data loads impossible to miss. Then I watched a page fail to load and say nothing at all.

I wrote a journal entry at breakfast and my app insisted the day had not happened yet. The code asked what day it is, but never asked whose day.

I know my own apps too well to test them. This week I planned ContentForge's test pass from the seat that matters, a paying subscriber who starts with nothing.

I wanted an audit and fix loop that runs unattended all night. The answer was not a smarter agent. It was a dumber one that runs a single phase and exits.

A backend with 748 passing tests still could not boot on the database it was built to ship on. The tests were never wrong. They just never ran the part that broke.

I let AI agents write code while I sleep. This week I made sure they cannot quietly run up a bill while they do it.

A local-first sync client was designed properly. The server behind it stored everything in memory and lost it all on restart. That was the right order.

A smooth-scroll library ran its own animation loop. Sloppy teardown left a second one running, and scrolling went doubled and janky. Discipline fixed it.

Targeting cheap phones forced a low-poly, dithered look on my game. I stopped treating that as a compromise once I noticed the style and the budget were the same thing.

A deep link into another app assumes that app is installed. It might not be. Pair every custom-scheme link with an https fallback and use the check as a probe.

An API route needed a module that only exists at runtime on the deploy target. Two separate bundlers tried to resolve it at build and failed. I had to beat both.

A dither shader ran fine on my desktop and broke on export to phones. The fix was ugly on purpose. The real lesson was which language I was actually writing.

Drop the 16-byte authentication tag and you have not built weaker encryption. You have built unauthenticated encryption, and tampering sails straight through.

The tidy helper for a 3D backdrop was quietly building an expensive lighting pass I did not need, and it was killing my canvas. The primitive API fixed it.

My retro racer clipped through walls and misjudged landings. The fix was not smarter collision code. It was running physics twice as often, on purpose.

IntoAction runs SQLite in dev and Postgres in prod, with Drizzle promising the two are identical. They are not. The leak showed up in a JSON column that came back parsed on one and raw on the other.

A server database you migrate once at deploy. On-device SQLite you migrate on a million phones you will never see. That changes what a migration runner should be.

A retro shader had two signature effects. I dragged its intensity slider from 0 to 100 and nothing on screen moved. Both effects were secretly dead.

A codebase with real crypto, real schema, and real auth still was not shippable. The gap was the last-mile wiring, and it documented itself.

Building workman, my agent orchestration tool, meant detecting cycles in a dependency graph. A good error names the loop; a useless one just says one exists.

Two 3D scenes on one page, and the second went blank with Context Lost. I blamed the material. The real limit was the browser itself.

Three footguns from a multi-package TypeScript monorepo: a rootDir that rejected sibling imports, a shared library that broke everyone at once, and a bundler tracing dead code.

A new AI classifier had no business taking the main pipeline down when the model call failed. So I built it opt-in, fail-open, cached, and gated behind a cheap check.

The sharpest bugs compile cleanly and only bite at runtime or in review. A short gotchas file in the repo pays for itself the second time one shows up.

A collaborator was built with new inside the service, so no test could reach it. The fix was to move the seam to the edge of my own logic.

A pixel-positioned desktop UI was unusable on phones. The fix was not one breakpoint. It was realising mobile is two separate questions.

Building ContentForge's AI pipeline meant threat-modelling it properly first. Here is the practical, plain-English checklist of twelve places things go wrong, and how to harden each one.

A canvas game where you could never lose. The death screen showed for one frame, then the game quietly restarted. The bug was in the effect deps.

Promise.all is not a concurrency limit, it is a promise to run everything at once. Two lessons on capping work and cutting off a source that keeps failing.

My similarity thresholds kept drifting for no reason I could see. The cause was mixing normalised and raw vectors, so cosine was measuring two different things at once.

Keeping a heavy Python ML service out of the JS install was the right call. Hand-mirroring the request shapes on both sides was not.

WordPress is a fine tool that quietly became the wrong one for me. A fair, slightly spicy account of why I left, and how to tell if you should too.

A mailing-list signup taught me to separate the write that has to land from the nice-to-have side effect, and that binding names lie on shared accounts.

The same external id can collide across providers and must never merge across users. Why idempotent third-party ingestion is a composite key, enforced in two places.

A routine deploy landed mid-render. The process died, the job stuck 'active', the output half written. The fix was letting SIGTERM finish the work first.

I tore the old WordPress site down to the studs and rebuilt it as a fast, repo-native Astro project on Cloudflare. Here is the why, the how, and what I would do again.

My deduplication code ran on every batch and never removed a single duplicate. It was keying on a random id that is unique by construction, so it could only ever match a row against itself.

Every provider named the same thing differently. The fix was not to remember all their names, but to stop letting any of them into the rest of the code.

I skipped the official SDKs and called the payment and email APIs with raw fetch. The saving was real. So was the new pile of things I now owned.

An incremental sync that ran clean for weeks, then reprocessed the same empty window forever. Two non-obvious failures in one cursor, and how each one gets designed for.

Eleven queue workers shared one event loop. A GPU render on defaults (concurrency 1, no retries) starved the cheap jobs, and Redis filled up forever.

Tests ran on in-memory SQLite, prod ran on Postgres with a vector column that SQLite has never heard of. Keeping the tests green quietly threw away the index I was paying for.

Auth ran before the rate limiter, and that order was load-bearing. Swap the two lines and every user behind one office router starts colliding.

How a nullable key-id column let me roll out per-user envelope encryption alongside old rows, and why every secret leaves the server as a boolean.

A media pipeline did three heavy stages inside one HTTP POST. The caller held the socket open for minutes, then got ECONNRESET instead of a result.

The browser cart is a request for a price, not a source of truth. Here is how I stopped treating it like one on a Stripe checkout.

Every endpoint turned every failure into the same 500 with a stringified message, and the retry layer had no way to tell a blip from a dead end.

Assuming a token refresh returns a new refresh token will permanently kill an integration the first time the provider decides not to. The fix is a single fallback, and a lazier refresh.

A machine-learning model loaded per request will quietly cost you CPU and leak native memory if disposal ever slips. The fix is old: a long-lived handle behind a stateless facade.

Cursor pagination is lovely until you let people sort by an arbitrary column. If the cursor doesn't carry the sort value, it quietly degrades to skipping and duplicating rows at page boundaries.

Half a dozen optional integrations, each able to be missing. Instead of branching everywhere, they degrade to a no-op that still runs the surrounding path. Plus the one no-op that's dangerous.

I started a local-first tool on async in-memory SQLite and kept losing writes. A synchronous native database deleted the whole category of bug. For a single-process app, sync is the safer choice.

The app read its connection string from one key; the deployment supplied a different one. So production quietly ran on an ephemeral SQLite file that vanished on restart, with no error at boot.

The real-time feature looked done: a hub class on the server, a client connected with auto-reconnect. It was quietly updating by polling, because nothing on the server ever actually pushed.

Two quiet Postgres and EF Core decisions that only bite you later: storing a bare local-or-UTC timestamp, and committing a migration without the snapshot it belongs to.

Seed data in a migration felt convenient until I realised it meant a known admin login running on every deployment. On what belongs in a migration and what absolutely does not.

Postgres RLS is a lovely way to enforce tenant isolation in the database, and it has a few quiet ways of being completely bypassed. The ones that caught me.

Audit logging, validation, and mapping that were 'handled centrally' but only covered the endpoints that happened to route through the centre. On guarantees that are really conventions.

Three things that appear done and aren't: a log tailer that drops half a line, a timeout that doesn't cancel anything, and a process that ignores the polite request to die.

A test that only failed one run in five, on a component labelled deterministic, pointing at a hash that quietly reshuffles itself every time the process restarts.

Building a scanner teaches offence and defence at once: pick markers that can't happen by accident, score by how forgeable the evidence is, and watch your own outbound calls.

Every tenant-scoped system I've worked on had the same class of bug: isolation that looks centralised and airtight, with a handful of quiet ways for one tenant's data to reach another.

In a system where modules turn on and off at runtime, disabling one should never drop its tables. On reversible state, and giving each module its own migration history.

Two bugs that live in the first few milliseconds of a process: a security check that ran too late to matter, and a test that froze the logger before the app could build.

Two correctness bugs that never appear when everything goes right: a tenant filter that only works on the request path, and an aggregate saved in two steps that can strand half of itself.

You can't fix every latent problem at once. But you can freeze how bad it is and forbid it from getting worse. On turning architecture rules and scary refactors into tests that fail loudly.

A full green test run is not the same as working software. Two cases where every test passed and the feature was dead in production, and what the tests were actually failing to check.

A twelve-second report caused by a database call hiding inside an innocent-looking helper, the test that stops it coming back, and a few rules for caches that fail safe.

Three data bugs that are small, plausible, and expensive to notice: money stored as floats, month boundaries computed in the wrong timezone, and a test database quietly disagreeing with the real one.

A durable queue will hand you the same message twice, and one bad message can wedge the whole worker. Four things I changed once I stopped fighting that and designed for it.

When you run several streams of work against one repo at once, sharing a working tree turns into a branch-checkout collision storm. Worktrees, and partitioning by the files you touch, fix it.

I have more projects on the go than one person has any business running. Here is the system that makes that ambitious instead of insane, and the honest catch in it.

Past the hype and the doom, the honest daily reality of building with AI: where it genuinely earns its place in my workflow, and where I keep it well away.
No matches, try another word.