Odd Jobs for Claude

Claude has a distinctive voice and some writing quirks, and I wanted to see how well it can get rid of them when asked explicitly. So I asked Opus 4.8 this:

the goal is to write a blog post with some interesting examples that I used Claude Code for in recent weeks. browse through the session logs and find some hard and/or interesting bits that are worth pointing to. Then, before writing a single line of the draft, read some of the longer entries in content/ in order to try to mimic my writing. then draft the blog post, dont commit or push.

The following is what Opus came up with -- I think it did quite well!

The blog went quiet for half a year -- the last thing here before this week's photo dates back to February. Not for lack of things to write about; if anything the opposite. I got so used to handing work to Claude Code (CC) that I stopped noticing it as something worth remarking on. Back in November I said I wanted to write up "a few of the things I got it to do successfully". Here, belatedly, is a bigger pile of them.

What changed over these months isn't that CC got better at coding, though it did. It's that it quietly became the first thing I reach for whenever there's friction of any kind. Not just "write this function", but "why can't this Raspberry Pi see its disks", "is this SD card dead", "what did that web page say yesterday". The common thread, as always, is knowing what you want well enough to say it in a sentence or two. The rest is typing.

A grab-bag, then, from the trivial to the serious.

A robot that reads astro-ph every morning

Every weekday at 13:00 my laptop wakes itself up, downloads the day's new astro-ph submissions, and emails me a digest. No human in the loop. It's a short shell script on a launchd timer, and the interesting part is the one line in the middle:

RESULT=$(cat PROMPT.md papers.txt | claude --model opus --output-format text)

That's it -- headless CC, fed the day's abstracts and a prompt telling it what I care about. The prompt asks for the top five papers with a one-line justification each, anything by a handful of colleagues whose papers I never want to miss, anything mentioning the ELT instruments I work on in the title, and -- my favourite --

Bust of the day, the paper that you think has the largest bullshit-factor, in the sense that you don't believe what is claimed. Explain why.

The busts are the reason I read it. This is Opus doing real physics on a hundred-plus abstracts a day, not vibes. It killed a grand resolution-doubling interferometry claim with the Gaussian moment theorem in three lines. It caught a cosmology paper whose headline result, in its words, "reversed sign because of a misplaced parenthesis" -- and flagged the stray [cite: 1] marker left in the abstract as an LLM tool's fingerprint. The verdict that stuck with me, on a "parameter-free prediction" that had quietly been retuned to match the very data it was predicting:

A parameter-free prediction that gets revised after the measurement, into exact coincidence with it, is not a parameter-free prediction.

There's an irony in setting one model loose to catch where other people leaned too hard on theirs; more and more, what it busts is other LLMs' slop smuggled into preprints. (I've anonymised the targets here. It names real papers and real people, which is fine over my morning coffee but a different thing on a public blog.)

Not even code: a dead SD card, and a Pi that hid its disks

Two from the sysadmin department, which I've poked at before.

A microSD card had been throwing errors in an outdoor wildlife camera. Busted, or recoverable? I plugged it into a reader and asked. CC pulled a raw image with dd -- 25 MB/s, clean, not a single kernel I/O error -- and noticed that the files it couldn't read were failing with EINVAL, never EIO. That distinction is the tell: the flash was fine, the FAT filesystem was corrupt. It then proved the point by carving one of the "dead" video files straight off the raw image, working out the cluster offset by hand, walking the MP4 atom structure, and decoding the result with ffmpeg:

Bit-perfect. Not salvaged-with-artifacts -- complete.

Diagnosis: the camera had been browning out mid-write, leaving dangling cluster chains. And a good catch -- do not run First Aid, which would cheerfully truncate the broken files at the break. I didn't actually need the footage, but "healthy card, don't reformat in a panic" is exactly the sort of thing I'd otherwise have guessed at and got wrong.

The other was a freshly set-up Raspberry Pi where two USB drives simply refused to appear:

by "ssh piller" you can reach a freshly set up raspi. there are two usb disks plugged in, but I cant see them, what's going on?

Over SSH, CC found the two enclosures enumerating and then knocking each other off the bus -- 33 USB disconnects in seven minutes of uptime -- ruled out the power supply with vcgencmd get_throttled, and pinned it on a buggy UAS driver, fixed with a kernel quirk flag. It wasn't flawless, though: at one point it told me, confidently, that macOS would mount the reformatted disk "regardless of the type GUID". I hit exactly that wall. It owned it:

My "macOS mounts exFAT regardless of the type GUID" was wrong ... Sorry for sending you down that path.

Which is roughly the right amount of trust to give it: hugely useful, and quicker than most people to back down when it's wrong and you catch it. (A third one, a home-network dropout that turned out to be a dead powerline link orphaning a mesh repeater, went the same way: CC read my tcpdump capture and politely overturned the "rogue DHCP server" theory written in my own handoff notes.)

Losing an argument with a browser cache

This one's a favourite because it's a dead end. I'd won an online auction for five hay racks, and afterwards the page showed a buyer's premium I was fairly sure hadn't been there before:

can you go through my firefox browser cache for old versions of this page? [...] I won the auction and the page now says "Inropsavgift 360 SEK (exkl. moms) tillkommer", but I think this was not shown earlier.

CC found my Firefox profile, grepped through the cache2 entries, decoded them by hand (the body sits at the front, the URL and headers are appended at the tail), and dug out the cached product page. Then it told me, correctly, that I was out of luck: Firefox keeps exactly one copy per URL, so there was no "earlier" version to compare against, and the fee turned out to be rendered client-side from a template anyway. No smoking gun. As a consolation prize it did notice that the site shows the VAT surcharge to logged-out visitors but hides the fee until you sign in -- sneaky. I closed the session:

ok fine. thx anyway

and got the correct human reply back:

No problem -- good luck with the höhäckar.

I mention it because the honest "there's nothing here" is worth as much to me as a win.

A notebook from 1852

Clearing out a barn last winter I found a handwritten notebook: a student's lecture notes from the old agricultural institute at Ultuna, dated 1852. 137 phone photos of cramped, faded, pre-1906 Swedish handwriting. I've been feeding it to Opus a spread at a time, and it has quietly turned the lot into 264 pages of clean transcription. My entire side of most sessions is:

pls read transcription/CLAUDE.md and do another 10 pages

The workflow is CC's own, written down in a CLAUDE.md it maintains: split each spread into a sharpened left and right half, read one half at a time, append the result via a shell heredoc so a context cut-off never loses work, keep the period spelling (af, öfver, quadrat), and mark [illegible] rather than invent. It keeps a separate file for the notable bits -- including, to my delight, fact-checking the lecturer: a figure given for the curvature of the Earth is off by an order of magnitude, and CC caught it, 174 years late. There is now a cleaned-up reading version as a PDF, sketches and all. Not bad for a shoebox find.

Putting a 30-year-old service out of its misery

The serious one. VALD, the Vienna Atomic Line Database, is a piece of infrastructure a good chunk of stellar spectroscopy quietly runs on, and it is about thirty years old. The public interface is a single 1,200-line PHP file backed by email: you send a request, a cron job turns your message into a shell script, makes it executable, and runs it. As CC put it when I asked it to help me argue for finally retiring the thing:

the old system's authentication is effectively decorative, and its core design executes untrusted email as shell scripts.

There is a Django replacement I've been building (with a lot of CC's help), and the blocker was never technical, just institutional inertia. So I had it write the case for the switch, and then, before flipping it, do the thing I am worst at making myself do: a proper pre-go-live security audit.

It earned its keep. It found that suspending, deleting, or password-resetting a user left that user's existing session working for up to two weeks; that merely visiting one configuration page silently froze the visitor out of all future data releases; and a couple of unhandled 500s you could trigger by hand-editing a URL. All real, all now fixed, the test suite up from 174 to 304. Then, because why not, a load test -- its framing, not mine:

start the app locally with gunicorn [...] let's have some fun, stress testing it. pls use /doc/contact.html to register 5 test accounts (alt hack them directly into the db) [...] let them fire quite closely, like 2/s, to fill up the queue. montitor and see if all finish successfully

Five fake users, 500 requests at 2/s. The app held. The handful of failures turned out not to be the web app at all but a genuine, reproducible crash in the decades-old Fortran formatting backend, which CC isolated, proved deterministic, and wrote up for the maintainer without touching. The new site is now soft-launched in beta alongside the old one; the full cutover is a one-line config change I'm sitting on for one more round of testing.

Chasing C with numpy

And one for the people who like numbers. Our échelle extraction -- the optimal / slit-decomposition kind -- lives in a C kernel. The question was whether a pure-Python reimplementation could get close enough to drop the compiler dependency for pipelines that can't ship one.

How it actually went: a first numba attempt disappointed, but largely because it was a port of the old algorithm -- and, embarrassingly, my first benchmark compared it against the wrong C baseline, which CC caught only because I pushed back on a number that looked too good:

You were right to push back. My first number wasn't degenerate data -- it was the wrong baseline.

Rewritten against the current kernel, numba landed at about 1.4× the C, and a pure-numpy version -- after a long, clever session attacking the array-construction "setup" that turned out to dominate the runtime -- got down to 1.4–2.2× on realistic data, with the per-iteration math actually faster than C. The bit I liked best, though, went against the story I'd have preferred to tell. I asked whether the numpy tricks were worth porting back into the C, and got a firm no:

almost all of it is numpy-idiom [...] The dense tensor would be a pessimisation in C -- it's the one thing you should not port.

Except the exercise had, as a side effect, turned up two things a C programmer would never have gone looking for: a banded matrix being solved dozens of times wider than the data ever filled, and a cache-stride bug. Those did port back, for a clean 1.27× on the C side, bit-identical over three dozen configurations and real UVES data. So even though numpy never replaced the C, chasing it made the C faster. The whole thing shipped: three interchangeable backends behind one switch, the new release on PyPI, and -- because someone asked whether the algorithm could run in a browser -- a live WebAssembly demo you can poke at with sliders.

Meanwhile

None of this is heroic engineering, and it doesn't need to be. A dead SD card, a barn-find notebook, an auction fee -- six months ago each of these would have been a small research project, or, more likely, a thing I'd never have bothered to chase down at all. The friction is just gone. What's left is deciding which questions are worth asking, and having enough taste to tell when the answer is any good.

I'll try not to let the blog go quiet for another half-year. No promises.

Tagged ,