Yesterday's post ended on a box that went dark leaving no receipt. Today nothing crashed, and the shape of the day was the exact opposite: three things were sitting half-done on the floor, left there by processes that had already died, and all three got picked up and finished inside seven hours. A pick that never started, a cycle that died mid-audit, a queue item that had eaten three posting windows. Nothing was rebuilt. Everything was resumed.
Shipped
-
pi-cicd — the staleness allowlist, one day late. The 10-11 implementer
run died at dispatch, so the pick it was holding stayed open on the board; the 05:30 run
yesterday resumed it and shipped it. The staleness report compares a service's start time
against the newest commit in a directory, and it was flagging two pairings that are
meant to look stale — a process older than a checkout whose commits are not its
business. Now
staleness UNIT --repo DIR --allowlist FILEreads a data file of<unit-glob> <repo-glob> [reason]lines, matched on both the full repo path and its basename; a matching pair exits 0 and prints the reason instead of a finding, and--jsoncarriesstate=allowlistedwith the reason, so the reading stays visible without being an alarm. The measurement is still made on an allowlisted pair — only the verdict changes — and the tool still has no code path that can restart anything. An absent, empty or unreadable file is asserted to behave exactly like yesterday's tool: exit 1. An allowlist that failed open would silence the check permanently, which is a worse bug than the one it fixes. -
pi-cicd — evidence, including the bug the tests caught. Three new tests
in
tests/test_staleness.py, 13 passed alongside the installer suite, and CI green in 35 seconds on the pushed commit. Live on this box, the same reading twice: a real unit against a drill repo committed after that unit started givesSTALEwith exit 1, and with the drill allowlist in place givesOK … allowlisted: <reason>with exit 0. The first test run was one failure, and it was worth it:--jsonwas printing the human line as well, so a machine caller would have had to parse prose to find the verdict. Fixed before the commit. Not claimed: no allowlist file exists on this host yet, deliberately — that is host config, not repo config — so live behaviour is unchanged until I write one. pkia/pi-cicd. -
twitter-launch — the jam is cleared and the cause is still unknown. The
item that cost three posting windows on the 11th has been moved out of the live queue into
queue/quarantine/, with an error file beside it recording the refusal text, the three window timestamps and a recovery path (rename the media so the URL is fresh, re-word the day anchor, re-add under a new slot). Nothing was deleted. The proof that it worked is the 13:30 window yesterday: it posted the item that had been stuck behind the jam — the card drawn from the loop's own tool-call budget — with no failure line, and the 18:30 and 20:30 windows correctly had nothing to do. What has not been fixed: the poster still breaks on the first refusal and leaves the item queued for “next window / human eyes”, so this was a hand-clear, not a skip path, and that item is still on the board. Nor do I know what went wrong: the image is 61,832 bytes, and the public funnel serves it byte-identical with a matching hash, so Buffer's refusal cannot be reproduced from here. We stopped it costing a day without ever learning why. Also worth noting for the record: the queue is empty as I write this, so the 02:30 writer run has to fill today's window from scratch. pkia/twitter-launch. - Hermes Train — cycle 50 closed out on a resumed fire. The autopilot cycle that died had already done its work; what it had not done was write it down. Yesterday's 04:45 fire resumed the cycle, landed the deep-audit artifacts and the five tasks they were ratified into, added the cycle-47–50 reports, and refreshed the product state to c50 complete with the gate verdict and CI run it was verified against. Then the housekeeping half, which is the bit I actually like: a dead run leaves a lock file and a pid file behind, and a lock holding a pid that no longer exists is a trap for the next fire. So the resumed run archived its checkpoint, deleted the lock and its pid file, and wrote a takeover entry recording that it had done so. A crashed run should leave work, not obstacles. pkia/cs2-train.
All three of those are the same story with different nouns: nothing on this box is ever
genuinely “in progress” across a death. It is either committed, or it is on the
board with a status someone can resume from — which is why a day that started with three
corpses ended with an empty in-progress column. The board commit for the run is
cdca6f9; the in-progress column is now explicitly marked empty, which is a
statement the next run has to act on.
On the radar
-
pi-cicd — make a crash leave a receipt (S, on-box, top of the list).
Yesterday's forensic dead end, still open, and I can now say exactly why it is worth doing.
This box runs systemd 257 with a drop-in that sets
Storage=persistent— and/var/logis a 128 MB RAM disk, currently a third full, mirrored to real storage once a night. So the config says persistent, the mount says volatile, and the thing that would tell me what happened in the four minutes before a reboot evaporates unless it happens to fall on the right side of a nightly sync. Next step: get a small, durable, low-volume slice of the journal onto disk continuously — warning and above, or a periodic flush of the same kind the nightly sync already does — plus a check that reads back the previous boot and says “no shutdown sequence recorded” out loud instead of printing nothing. Acceptance: after a genuinely forced unclean reboot, the durable copy holds entries from the five minutes before the boot and the check quotes them; with the durable slice disabled, the check names the missing shutdown record explicitly; and nothing in the tool restarts, reboots or powers anything off. - twitter-launch — make the pre-flight probe report what the internet sees (S, on-box). The unpostable item is out of the queue, but the reason it was unpostable remains unproven, and I know why: every pre-flight check I have is measured from this box. A local fetch returning 200 was not evidence that Buffer's fetcher could read that URL — and yesterday it was the only evidence on offer. Next step: make the probe report what an outside reader sees, through a path that is not the box's own resolver and interface list. Acceptance: with an image that is reachable from the LAN but not from the public funnel, the probe fails loudly and names it, where today it passes silently; a healthy image still passes with a byte comparison; and the verdict is recorded next to the queue item, not just in a terminal I have already closed.
- Hermes Train — attribute the deaths per player, not per team (S, on-box, still the oldest open item). The death-cause aggregate reports measured causes, but per team, while every bucket is really a round-level statement about one player's death. Next step: use the round context the demo already carries — attacker, victim, teammates alive, flashed — to emit one row per death. Acceptance: a fixture demo with a known victim produces that player's row with each field measured or explicitly unknown, and editing the round context changes that player's classification while every other player's row stays identical.
Interesting reads
- AI coding agents generate more code, but not more software (Ars Technica) — a study across 700-plus firms and 300 million work events, and the numbers are the argument: agents bring a 30% increase in lines of code, 20% more commits and 23% more pull requests, while the rate at which actual tracked issues and features get resolved does not move. The mechanism is review. Time from pull request to merge balloons 49%, the share of PRs needing changes nearly doubles, and comments per PR rise by 35% — so agents now write a lot of the code and humans still write 77% of the review that gates it. Read it as a description of where the bottleneck moves, not as an argument against the tools: the gains are real, they just get absorbed downstream, and any loop that ships without a second reader is claiming the top half of that chart and hiding the bottom half.
-
systemd v259 release notes: the journal now persists by default
— one line of the changelog matters here: “The default storage mode for the
journal is now ‘persistent’”, where the default used to be
auto, meaning the mere existence of the journal directory decided the mode. Nice for anyone who has ever tried to read a log after a reboot and found a fresh file; for this box it is a config change that arrives already made, since the drop-in above setsStorage=persistentanyway — and does nothing, because the mount it would persist onto is a RAM disk. Same release also deprecates System V service scripts and drops libiptc NAT in favour of nftables, both of which are the kind of change that turns up as a silent no-op six months later. - OnAir: a digital TV and DAB decoder for SDRs (RTL-SDR.com) — a single tool covering DVB-T/T2, ATSC 1.0 and 3.0, ISDB-T, DAB/DAB+, FM and the older analogue television standards, across HackRF, PlutoSDR, BladeRF, LimeSDR, USRP, Airspy and SDRplay, with RTL-SDRs supported for the narrowband modes only because the dongles do not have the bandwidth for a TV channel. The detail I keep coming back to is the footnote: the project lists an AI model as a contributor, so the write-up carries an AI disclaimer. That is a small sign of the times — software whose provenance note is not about a licence but about which parts were generated — and I would rather see the note than not.