Skip to main content
  1. Categories/

Development Environment

The installer stamped an ID it never read. It reinstalled over itself.

··1376 words·7 mins
An installer writes a script. It stamps that script with `HERDR_INTEGRATION_ID=claude`, an identifier that exists for no reason other than to say "I made this". Then, to decide whether it has already run, it ignores that stamp entirely and compares the rendered command string in a config file. Rewrite the command to an equivalent form and the installer no longer recognises its own work, so it installs a second copy alongside the first. I hit three bugs in three days. They looked unrelated until I wrote them down next to each other: one in someone else’s tool, two in a plugin of mine. They share a shape, and the shape is worth more than any of the three fixes.

launchctl bootout returned 0. The label was still there. Bootstrap failed 5.

··1155 words·6 mins
`launchctl bootout` exited 0. `launchctl bootstrap`, on the very next line, failed with `Bootstrap failed: 5: Input/output error` because the label was still loaded. Then the bootout finished on its own schedule and the label went away, leaving a machine with no daemon at all and an installer that had just told me it was done. This one is mine: I caused it while fixing something else. 🧪 Tested with launchd · macOS I have written before about launchctl unload returning 0 while the daemon kept running, which turned out to be KeepAlive’s supervisor racing the teardown. This is the sibling. Same API, opposite failure, and where that one was launchd surprising me, this one I built myself.

The tunnel was up. The socket existed. Nothing was on the other end.

··1851 words·9 mins
The daemon reported the tunnel as established. The local socket file was right there on disk with the right permissions. The target sat at `offline` forever. Meanwhile `connect to /home/nick/.config/herdr/sessions/main/herdr.sock port 0 failed` was being written to a stderr stream that nothing in my process ever read, several times a second, for as long as I left it running. Deleting ClaudeDeck · Part 5 of 5 1 2 3 4 5 🧪 Tested with herdr 0.8.0 · macOS HerdDeck drives agents on a second machine. My laptop has the Stream Deck and the daemon; the desktop runs herdr and the agents actually doing work. They are joined by ssh -N -L <local.sock>:<remote.sock> host, a Unix-domain socket forward, and the daemon speaks the same protocol to a forwarded socket as it does to a local one.

The ring was 1.00:1 against its background. The key read 35% at 65%.

··1541 words·8 mins
A key on my Stream Deck showed an agent with a context window 65% full. The ring around it displayed a little over a third of a circle, which anyone would read as 35%. Not blank, not obviously broken, no missing-data placeholder. Confidently wrong, by exactly the amount that inverts the decision you are making when you look at it. Deleting ClaudeDeck · Part 4 of 5 1 2 3 4 5 🧪 Tested with herdr 0.8.0 · macOS That is the hardware, live against five agents on a remote machine. The top row is one key per agent. The tint is that session’s lifecycle state, and the ring is how full its context window is. Two independent signals, one key.

Five wrong answers in one day. One nearly deleted 10 GB of coursework.

··1903 words·9 mins
`mdls kMDItemLastUsedDate` returned `(null)` for Microsoft Word. I read the null as "never opened" and put Office on a removal list: 10.1 GB, four apps. One last check saved me. My home directory held 150 Office documents, a conference presentation edited two weeks earlier, and a PowerPoint lock file, which only exists while the file is open. The proof that the null was misleading had been sitting in my own diagnostic report for an hour. That was one of five. In a single day of hardening this machine, five different tools told me things that were not true. None of the answers looked like an error. Each one arrived as a clean, confident finding, and under each one a check had quietly failed or asked the wrong question. All five had the same shape underneath. Once I could name the shape, I stopped falling for it.

Three layers of secret defense for a public dotfiles repo. One was decorative.

··1190 words·6 mins
My dotfiles repo is public, which means any slip with a credential is permanent. History rewrites do not un-leak a key that a scraper already saw. So the defense cannot be one layer, and the interesting part of layering is not the count of tools. It is that each layer intercepts at a different moment: one before the commit exists, one at the moment of push, one sweeping the entire history in CI. The uncomfortable part, and the reason this post belongs to this series: one of my three layers used to be a decoration.

My commands vanished with exit 0. The culprit was a file named env.

··1441 words·7 mins
`env -u VAR command` did nothing. Exit code 0, no output, no error. A different command, same thing. A different variable, same thing. Any invocation that started with `env` just quietly evaporated. What finally made the problem visible was a `git init` that reported success while creating no `.git` directory at all. That was a year ago. The case closed last month, and the culprit was not uv, not some third-party installer, not anything exotic. It was this repo’s own bootstrap script. The thing that caught it was the regression test I had written for the original incident.

My dotfiles had a no-exceptions test gate. It had never run once.

··1331 words·7 mins
My dotfiles repo has a CLAUDE.md, and the CLAUDE.md has a rule in bold: every commit must pass the test suite, no exceptions. Within the first hour of an audit this July, I learned that this rule had been enforced exactly zero times since the day it was written. The hook file existed, its contents were correct, it even had its executable bit. It was just sitting at a path that yadm stopped reading a major version ago. No error message. No warning. To yadm, a hook in the wrong place and no hook at all are the same thing.

My diagrams rendered on refresh and vanished on click. The head never loaded.

··1010 words·5 mins
A reader clicking from my homepage to a post with a diagram got a block of raw mermaid source. The same reader pasting that post's URL directly got a rendered diagram. Same page, same build, same browser. The difference was the click, and the bug had been live on this site for months across every diagram, every math formula, and every chart, because I had only ever tested pages by loading them directly. 🧪 Tested with Blowfish 2.10x · htmx 2.0.10 Two features, both reasonable, one collision # This site has htmx’s hx-boost on the body: internal navigation swaps page content in place instead of doing full page loads, which keeps transitions smooth. Separately, the Blowfish theme is smart about heavy libraries: mermaid, KaTeX, and Chart.js bundles are only included on pages that use them, injected into the <head> of exactly those pages.

My blog publishes one post a day. I haven't touched the deploy in weeks.

··1104 words·6 mins
In July I went three weeks without opening my blog repo. During those weeks it published two posts, on schedule, each one confirmed live by an automated check, and the only reason I know all this is a green history in the Actions tab. The system's single notification channel is a failure email, and it has never fired. This post is the full pipeline, including the parts that only exist because something went wrong on the way here. 🧪 Tested with Hugo 0.164.0 · Cloudflare Pages The one Hugo fact everything hangs on # Hugo skips content dated in the future unless you pass --buildFuture (docs). That single default turns the date field into a release valve. Merge a post dated next Tuesday and production simply does not contain it: not in the sitemap, not in RSS, not at its URL. It sits in main, invisible, until a build happens after its date.

Do not restyle a deck to look official. Wipe the template and keep its masters.

··907 words·5 mins
The last deliverable of my summer project was a slide deck in the official university template. I generate slides from Python, and my first instinct was to rebuild the branding by hand: sample the navy, find the fonts, redraw the footer. That path produces decks that look almost right, which is worse than wrong. The trick that works is to load the official .pptx itself, delete every sample slide while keeping the slide masters, and add my own slides on the official layouts. Backgrounds, fonts, and logo come along for free. Auditing AI-Solvability · Part 5 of 5 1 2 3 4 5 The generator is one Python file, roughly 470 lines of python-pptx, and it built the final presentation for the auditing project from part 1. This post is about the two things in it worth stealing and the one bug worth laughing at.

A crash is never a pass. Three rules that kept 1,095 eval runs honest.

··1099 words·6 mins
The scariest failure mode in an eval harness is not a wrong answer. It is a harness problem wearing a model problem's clothes. A test file that does not run looks exactly like a problem no model can solve. A sandbox flake looks exactly like a failed attempt. Before I trusted any number from my CS1 auditing tool, I had to make those confusions impossible, and it came down to three rules. Auditing AI-Solvability · Part 2 of 5 1 2 3 4 5 Part 1 of this series reported scores from 1,095 sampled solutions. This post is about why I believe those scores. The harness lives in one Python module, and its docstring is a contract I wrote before the code: