Nine repos moved today. One is public and got its own post; the rest is private work, so what follows is the reasoning and the patterns with the specifics filed off.
Reading back the day, almost everything landed on the same theme: a rule that existed in two places, and the two places disagreed. Not one of them was a hard bug to fix. Every one of them was hard to see, because both copies were individually reasonable.
A clean merge that was a fatal
A feature branch added a helper predicate — call it runsNatively() — because the main branch didn't have it yet. By merge time, main did. Two implementations, same name, same class.
Git reported a clean merge. PHP reported Cannot redeclare.
That part is only mildly interesting; the interesting part is that the two implementations weren't equivalent. One asked whether the resolved driver was the SSH driver with containers disabled. The other asked whether the provider type was "ready". Those coincide most of the time, and diverge on exactly the case that matters: a bare-metal provider with no SSH credentials, where the resolver falls back to a fake driver that deploys nothing. The second version answers "yes, native" for a machine that isn't going to run anything at all.
Two functions with the same name and 90% overlapping behaviour is worse than two functions with different names, because the name promises they're interchangeable. The one that survived is the one whose logic mirrors the resolver's own branch — it doesn't re-derive the answer, it asks the same question the runtime asks.
The rule I'd write down: if a predicate exists to predict what another component will do, it should be built from that component's own condition, not from an equivalent-looking one.
The wizard offered what the runtime would refuse
Same system, related bug. A deployment guard correctly refused a certain workload type on a machine with no container daemon. Correct refusal, wrong location — it lived deep in the runtime, at step 15 of a 21-step pipeline, after infrastructure had already been provisioned. So the whole chain rolled back.
The pipeline had a preflight step whose entire stated purpose is "fail at step 1 on anything the later steps would fail on anyway, before a single resource is reserved." It just didn't know about this rule. And the wizard that offered the combination didn't know either.
Three components needed the same answer, so the rule moved to one place all three read. The predicate is answered from the database row alone — no SSH session, no live node, no side effects — precisely so the UI can ask it before anything exists.
There's a second lesson hiding in that fix. The failure panel had an automated hint suggesting "enable a container runtime and re-provision." Following that advice would have turned a loud, honest failure into a green deployment with nothing actually running on it. When you write remediation copy, check that the remediation works — advice that converts a visible failure into a silent one is worse than no advice, and it deserves a test pinning it just like any other behaviour.
The portal said yes, the action said no
Different product, same shape. A governance module computed eligibility — can this member vote, can this member hold office — in more than one place. The UI computed it one way and the write action computed it another, so the portal cheerfully offered a ballot that the action then refused. A user-facing lie, generated entirely in good faith by two correct-looking functions.
Both collapsed into one rule each, and one detail worth stealing: the office-eligibility rule now keys on the office code rather than on a hydrated object. Comparing codes is stable across a re-fetch, a cache, a serialized queue payload; comparing objects quietly depends on which instance you happen to be holding.
Related, and my own convention biting me: a filter compared internal auto-increment ids against public UUIDs. If you run UUID public identifiers alongside integer primary keys — I do, on almost everything — that's the standing trap. Both columns are "the id", both are truthy, and a comparison between them just returns an empty set instead of throwing. Type the boundary if you can, so the mismatch is a compile-time complaint and not a silently empty list.
And a counting bug in the same area, which is the same class again: a tally's denominator has to be the same roll as the quorum's denominator. Two ways to count "who is eligible" and you get a result that's individually defensible on both sides and adds up to nonsense.
Tests that lie, and tests that cost
Two test-suite fixes today, both about isolation.
Parallel workers stepping on each other. Seven tests failed under --parallel with SQLite reporting "attempt to write a readonly database". Two places created per-tenant SQLite files: one already put the process id in the filename, the other used a shared directory with a per-test counter. But the shutdown handler globbed the whole directory — so the first worker to finish deleted databases the other workers were still writing to. SQLite reports a deleted-then-reopened file as readonly, which is a confusing symptom for "someone else unlinked your file."
The fix is a single function that produces the prefix, used by both the naming and the sweeping. Now what a process creates is exactly what its glob matches, and they can't drift apart — the same one-rule-one-place move as everything else today.
There's a trade-off in that fix worth being explicit about: narrowing the sweep to one process means a run killed with Ctrl-C leaks its databases forever, because nothing else prunes that directory. So a startup sweep collects orphans, using age to distinguish an orphan from a live sibling. Workers in a running suite are seconds old; the suite finishes in minutes; an hour of stillness means the owning run is gone. Result: roughly 8 minutes down to 4.
A test that depends on your DNS is not a test. A preflight step reached a real dns_get_record() call. Serially, it was slow. Under --parallel on a loaded machine, workers blocked on resolver timeouts and the suite went from about two minutes to about nineteen, with nine failures that pass fine on their own. The lookup is now bound to a no-network stub in the base TestCase, so a new caller can't reintroduce it by accident. Binding the stub centrally is the point — a per-test mock only protects the tests that remember to mock.
A 41-second invoice
My favourite of the day, because the symptom pointed nowhere near the cause. Three billing test files hung and the suite couldn't finish. Not a test problem: rendering the brand logo into an invoice PDF took 41 seconds, measured directly.
The logo was 2434×2304 — 5.6 megapixels — displayed at about 120px wide. dompdf composites image alpha in userland PHP, so cost scales with source pixels, not display size. Downscaled to 300px: 1.2 seconds.
Which means every invoice PDF in production — issued on activation, regenerated on every download — was burning 41 seconds of queue worker or request time. A test-suite annoyance was the only reason anyone looked.
The guard is the interesting bit. Not a timing assertion, which would be flaky on CI and would only tell you that something got slow:
it('keeps branding assets small enough to render', function (string $path) {
[$width, $height] = getimagesize($path);
expect(max($width, $height))->toBeLessThanOrEqual(600);
})->with(brandingAssets());
Assert the property that caused the problem, not the symptom. Someone re-exporting a print-resolution logo in a year gets an immediate, explainable failure instead of silently restoring a 40-second invoice.
Forty copies of a 21 MiB model
On the mobile side (Dart/Flutter, but the shape is framework-agnostic), each AI opponent persona was loading its own copy of an inference model. The asset bytes were cached, but every persona still built its own native session with its own weights and arena — about 21 MiB each — and the provider deliberately isn't auto-disposed, because a game might be resumed later.
Forty distinct persona configurations therefore meant up to forty resident sessions, created lazily on each persona's first move, so the memory spike lands mid-game and the process gets killed with no stack trace to show for it.
The realisation: the parameters that differ between personas were per-call arguments, not session state. Every persona was running inference against byte-identical weights. One shared session now serves all of them.
The part I'd call the actual design work is disposal. A shared resource handed to many holders needs an explicit answer to "who is allowed to close this?", so the object carries an ownership flag — true when it loaded its own, false when it borrowed the shared one — and dispose() respects it. Without that, the first persona to be torn down takes the engine down for everyone still playing. And the test drives all forty configurations through a counting loader and asserts it loaded exactly once, which is the only kind of assertion that actually pins a caching claim.
Also, from the same repo, the same theme one more time: a provider that resolves once per app run was being read for mutable fields. Reading a stable identifier off a frozen snapshot is fine. Reading a value that changes during the session — and then validating a user's action against it — means the screen showed a cosmetic as unlocked while the controller, holding the start-up value, silently rejected the pick until the app was restarted.
Two small ones, filed under "look at what you actually shipped"
A requirements brief named a payment provider to build a driver for. Going to find its merchant API turned up: no published API, no webhook payload, no signature scheme — because it isn't a gateway. It's a billing SaaS that sits on top of six other gateways, the same way the product does. The interface docblock had it listed as "not yet written", which reads like work waiting to be typed up rather than work that can't exist. Before an interface promises a driver, confirm the target publishes something to drive. Otherwise you've written a contract that can never be satisfied, and left the next person to spend an afternoon searching for docs that don't exist.
And the smallest fix of the day, from an app heading to a store: an icon in the stock Material set is named after a franchise and draws that franchise's item. It had been picked because its name matched a verb in the product's vocabulary, and it sat on two of the most-viewed screens — and in the store screenshots. Stock icon sets aren't automatically neutral; a few of the glyphs are trade dress with a friendly API. Worth a pass over the icons before submission, especially in a category where IP gets read closely.
The through-line
Every item above is the same failure with different clothes: the same rule, written twice. Two predicates, two eligibility checks, two counting rolls, two naming schemes, two model sessions, two copies of a player's state. In each case both copies were written by someone reasonable, and in each case the bug wasn't in either copy — it was in the gap between them.
The tell is when you catch yourself writing something that should agree with something else. That "should" is the bug report, filed early. Either derive one from the other, or put them in the same function and let there be nothing left to disagree.
Top comments (0)