Technical write-ups and stray thoughts from the Pearachute team.
A local model on a Windows dev machine answered every call exactly two seconds late, and changing localhost to 127.0.0.1 fixed it. The usual explanation, IPv6 first and then a fallback, is only half the story. The other half is that Windows takes two seconds to accept a refused connection. Here is the measurement, why Node never pays it, and how to tell whether your own setup does.
How pkanban gained an MCP server at /mcp so it works from the Claude chat apps as well as from coding agents. Streamable HTTP, stateless, every tool a call to the existing REST API, API keys for agents and OAuth with dynamic client registration for claude.ai. From the first commit to signing in through claude.ai took about two hours. Here is the build, and the SDK defaults we had to turn off.
Vagrant in 2016, Ansible in 2017, Docker in 2025. Each was the right answer to "how do I make this deploy repeatable" on the day it arrived, and all three were deleted in one commit for a shell script. The shell script is now bigger than all three combined. That's the honest part, and it's also the argument.
We build a mobile app that pays out for real dog walks, so its main input is a person outside with a phone. A desk rig that feeds GPS into an Android emulator found a crash within its first hour, in exactly the code every test fake had been built to skip. Where you put the fake decides which bugs you can find, and the next bugs come from whatever sits below it.
A Minecraft plugin has to declare the game API it was built for, and one afternoon ours declared a version that has never existed. That string was fixed in two hours. The Java upgrade that arrived with it took 116 days to notice, because the build that would have said so was red and nothing was waiting on it. The better answer was to record versions as measured facts instead of typing them as claims.
Twelve of the sixteen lines in this site's index.html came from the Vite starter template, and nobody touched the file for 1,297 days. The Vite logo was the favicon on a consulting site, and shared links unfurled as bare URLs. Every visitor downloaded a 3.6 MB photo that was drawn at 15 percent opacity. Scaffolding is built to go unnoticed, and that is exactly how it reaches production.
A reward code that works once is the smallest honest example of a check-then-act race. In a dog-walking app we build, the check lived inside the UPDATE from day one, and the row count picked the winner. Run against the same kind of SQLite database, the naive version redeemed one code 26 times on average with 32 threads racing. Three weeks later the same app shipped the race somewhere else, inside a transaction, which is the part worth reading twice.
One evening we removed what was left of this site from before its Svelte rewrite, 29 files deleted, 13 moved, and about 5,400 lines gone. The deleting took minutes. What made it safe was a short chain of negative findings, each one resting on the last. Age turned out to be no evidence at all, and the one file kept on purpose fails the same test.
One afternoon a multiplayer serpent game we build took eight fixes in under two and a half hours, and not one of them was for an error. A pellet sat inside a head without being eaten. Bodies were twice as long as the server said. Gravity felt like a conveyor belt. Nothing failed, because there were no tests, and the useful question turned out to be which side of the wire each fix belonged on.
For two years this site's contact form ran on the Gmail API, with three direct dependencies, twenty more underneath them, a refresh token, and read access to the entire inbox. Going back through the history turned up something worse. Not one browser submission ever reached the Gmail code, because XSRF checking had been switched on in 2021 for a form that never sent a token.
Job Wolverine's listing built all 8,608 jobs before it sent a byte, so the page got slower every time a crawler did its job. Streaming it put the first page on screen in 63ms. It didn't make the wait constant, because the sort still has to finish first, and it moved two problems somewhere harder to see: a total nobody sends, and an error that arrives as a clean 200.
This site served a blank page on every route, and not one log line said so. The repair carried five separate fixes: a mount that never happened, a fallback that couldn't catch, a route to a directory that didn't exist, a guard that guarded the wrong value, and a flag that needed an equals sign. What each silence was made of, how long each bug lived, and the one rule they left behind.
We write this site's code on Windows and deploy it to Linux, so the repo pins LF line endings on the files the server executes, in 35 lines of .gitattributes. The fix is right, but half the reasoning we gave for it was folklore. Tested on real Linux, a CRLF shebang is fatal and a CRLF GitHub Actions step is quietly worse than we claimed, while systemd and nginx strip the stray byte without complaint. What the carriage return actually does to each tool that reads it, and why a preventive fix's reasoning deserves a test of its own.
CI failed on a file that existed. It was finished, imported from five places, and described in the very commit that broke the build. A bare lib/ line from GitHub's stock Python template had been matching folders at every depth since the repo's first day, and git status, git add and the local build all stayed green for exactly that reason. How the silence worked, the two commands that expose it, and the check that proved there was only one victim.
A Python web app runs on exactly one interpreter, picked by apt on the server, while CI defaults to a three-version matrix that never touches it. Where that default came from, why it's coverage theater for an app, the reused-virtualenv drift that keeps the failure invisible, and the one-file fix that ties CI, the deploy and the server together.
Shrinking a realtime multiplayer snapshot took three passes (interest culling, delta encoding, binary packing), and the order is the argument. Each stage is cheaper to reason about than the one after it, and every stage came with its own measurement, including the one where culling did nothing at all.
We tracked all 85 files of the January 2011 tree through every commit since. Every code file is gone, seven of eight documents never moved, and the images survived by relocating. One git script, one chart, and the discovery that the code was scaffolding for the writing all along.
GitHub's squash button has had the same label since 2016. What changed is that the dropdown next to it keeps disappearing, and a workflow choice quietly became a policy field. What a squash merge actually does to your history, and what git can no longer tell you afterward.
A background worker that never embeds anything was still carrying 290 MB of PyTorch. The model was loaded lazily and correctly; the library import above it was not, and the two get confused constantly. How to find the same bug in your own project, and why the usual entry-point layout guarantees you have it.
A Python test suite around an app that talks to an embedding model and a hosted LLM turns up two silent failures: tests that never run, and tests that run against the live provider. What live inference in CI actually costs, and how to make the no-live-calls rule something that fails instead of something you remember.
A geodesic sphere is beautiful math until you have to map back and forth between position and triangle index. The geometry was fine; the off-by-one was in the indexing. Unit tests found in seconds what play-testing in the editor could not.
What actually happens when you try to set up a real test suite for a UE5 game: the three frameworks, the one that can't run on a launcher install, and the code structure that makes any of it worth doing.
Search on the job boards we run happens entirely in the browser, against a binary the server builds once and every visitor downloads. Why that's cheaper than it sounds, how ten thousand documents fit in a few megabytes, and the two mistakes in it that produce no error at all.
Three repositories, three different arrangements of the same two instruction files, and the one we'd put the most care into was the broken one. Why the priority layer has to live in the file every agent reads, and the rule for deciding what earns a place in it.
A library whose whole point is that it needs no build step can't be delivered by a bundler, so this site keeps a copy of it instead. What that copy gives up, what dependency injection has to do with it, and the two CI checks that buy the guarantee back.
What it takes for `install.sh` to go from a bare VPS to a running HTTPS site in a single non-interactive pass, on a shared box, for a private repo. Not any single hard problem, but seven small ones that don't announce themselves until you actually try to automate them.
Architecture school teaches a design process built on cheap, fast iteration long before anything looks finished. Software keeps reinventing the same lesson.
A pitch I've heard in a dozen forms over the years, personalization startups selling on how much they know about your users. The part that never quite adds up.
How to serve correct per-route titles, descriptions, and Open Graph cards from a Tornado backend in front of a client-rendered Svelte SPA, without a full prerendering setup.
A one-liner for running a real SQL query against a CSV file using the sqlite3 command-line utility, no database setup required.
A chart needed one number aggregated three different ways. Rather than writing it blind, we built it up interactively in the console, the way you'd work out a query at a SQL prompt.