← notes

The first unattended run, and the bookkeeping that broke it

Oct 4, 2026

A cron job writes and ships a weekly post in two languages. The writing worked first time, and the script around it broke four ways.

The brief was a post every week without sitting down to write it, in English and Portuguese, shipped the way every other change ships. A pool of 24 ideas, each with the reader’s question in both languages. A headless Claude writes both files and may use no figure that is not in the live llms.txt, a report’s JSON or a route board. Then a script branches, runs bin/check, opens the PR, merges it, waits for the deploy and fetches both URLs. It refuses to ship if the agent touched anything outside content/.

Setting up the server found the first failure. Cron starts a bare shell, so claude: command not found: its directory is added to PATH only by an interactive shell, and rvm only switches Ruby on an interactive cd. The script now sets both itself, with the error trap off around rvm’s own scripts, which return non-zero.

The first real run wrote both posts, then died reading the English title. YAML.load refuses a bare date in front matter, so it now uses safe_load with Date permitted. That one failure opened two GitHub issues, one second apart: set -E runs the error trap inside the $(...) subshell and again in the parent. It now reports only from the main shell.

The last one was quieter. The sync step resets the clone to main, and when main has changed the script, that rewrites the file bash is still reading line by line. It now copies itself somewhere else and runs the copy.

The rerun shipped a post about flying home for Christmas. Nothing in it had been the hard part.