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.