2026-08-11: Needs a Human

The alert had been arriving every fifteen minutes since Thursday afternoon: HA Green UNREACHABLE for 370 consecutive cycles (~92h). Needs a human. The ARP table still showed the appliance sitting exactly where it belonged, so nothing had drifted. The guardian had correctly refused to heal. It just wanted a human.
Bert brought it over with the detail that turned out to be the whole story: the guardian had refused to heal, correctly, and was still insisting someone come look.
So someone curled the appliance. It answered 200. Its frontend served. Its supervisor observer served. sanctumd, an entirely independent monitor watching the same box, had it listed as healthy — port 8123 open. Home Assistant had never gone anywhere.
The key it could not read
Section titled “The key it could not read”The guardian’s health verdict is an AND: the appliance must answer a ping and its API must answer an authenticated request. The ping succeeded every cycle. The API call went out carrying an empty bearer token — and the reason was sitting in the same log, twice per cycle, ignored for four days:
hag_token: REFUSING — ha-token is group/world-readable (mode 640); run: chmod 600The token file’s mtime was five weeks old, but its ctime was 13:00:04 on the day the alerts began. Not a rewrite — a permission change. That morning’s boot had run the installer from service principal wave-1, which walks the secrets directory and applies chmod g+r so the sanctum principal can read its own API keys. The comment in that script is explicit about the intent: group-readable, not world.
That migration keeps producing incidents, and never by being wrong. What breaks is code predating it that still assumes one human owns everything: The Green Lie was a service that could not write its own database. This is a monitor refusing to read a file it is entitled to read.
The token gate had been written to refuse 0044 — group-read and world-read, in one mask. World-read is a leak. Group-read, in a directory whose whole purpose is a shared service principal, is the design. The boot re-applied the design; the gate called it a breach; the bearer went empty; the guardian reported the house.
Timestamps closed it in one line. Last healthy cycle 12:49:38. First failure 13:04:42. Same day, one boot apart.
R2D2 did notice something. On the 8th it filed the guardian under reload-stuck-launchd-service — the healer’s read was restart it, and a restart relaunches the identical refusal fifteen minutes later.
The advice was worse than the bug
Section titled “The advice was worse than the bug”The refusal printed a remedy, and following it would have been the real outage. chmod 600 breaks every consumer running as the service principal — the HA bridge, the reporting job, the lighting toolset — and the next boot re-applies g+r, re-breaking the guardian anyway. A monitor that confidently tells you to sever six working services is worse than one that merely lies.
The gate now refuses actual leaks — world-read, and group-or-world write — and accepts group-read when the group is the service group. Five cases pinned: 600 and 640:sanctum allowed; 640:staff, 644, 660 refused.
That is the shallow fix. The deep one is that a credential failure was reported as a network outage. The guardian already had a guard for exactly this shape — if it cannot reach its own gateway, it declares the appliance’s status unverifiable and suppresses the alert rather than blaming the appliance. The token deserved the same treatment and never got it. It resolves once, up front, and an unreadable token now exits with a credential alert: no offline cycle counted, no heal attempted, no outage claimed.
Meanwhile, the house was locking itself out
Section titled “Meanwhile, the house was locking itself out”Chasing the guardian surfaced a second alert in a neighbouring log, quiet since at least the 8th. Every night at 23:00, Force Flow’s child-curfew stranger sweep was pausing 25 devices. Not a stranger among them: both access points and the router, four wall panels, the HVAC controller, the lighting bridge, the dishwasher, the AV receiver, the room speaker fleet, and the Home Assistant appliance itself. One of those speakers is the HomeKit hub — the house was cutting off the thing that automates the house.
The log pairs a 23:00 pause with an 09:00 release, which reads like a ten-hour blackout. It wasn’t — and that assumption reached this page’s first draft before anyone checked. A ledger orphan sweep, an unrelated safety net that releases any block no active state claims, caught every one within seconds: the appliance after 14, a wall panel after 34, the hub host after 40. Eighty-seven such releases sit in the log, and the 09:00 line is bookkeeping on a block already gone — one entry says so outright, bridge reports no block target.
Real cost: tens of seconds of dropped connectivity per device per night, plus a parent alert each time, plus a house depending on a safety net to undo a nightly mistake. Worth fixing. Not the outage the log’s shape suggests.
The sweep is a good feature with a real scar behind it: a renamed, spoofed console once parked itself online before curfew and slept through the join-time rule — the same unknown category that the Quest wore to get around bedtime. It quarantines devices whose category is unknown. Infrastructure is supposed to be excluded by an OUI list in config.
The list was right. It simply never reached these devices, because a device’s category is computed once, at first discovery, and then persisted. Every one of them had been stamped unknown months ago. Adding their vendor prefixes would have changed nothing at all — the config knob only ever worked on hardware bought after the entry landed. So the sweep now re-derives the classification from current config before it quarantines anything, which is what makes the config a control surface instead of a suggestion.
The fix that tried to open a door
Section titled “The fix that tried to open a door”The first version of that re-classification passed the device’s stored name into the classifier. It worked; every one of the 25 came back exempt; the tests were green.
A control case saved it. The classifier applies hostname hints, and a device’s name is supplied by the device. Renaming a console “Living Room TV” would classify it tv, out of the stranger set, and walk it straight out of the curfew sweep — handing the exact renamed-console evasion the feature exists to stop a brand new door, in the patch that was supposed to harden it.
It now re-derives from the MAC alone — hardware-rooted, and a rename cannot move it — and only ever rescues to infrastructure, never to gaming or tv or streaming, which have their own enforcement paths. Verified both directions: all 25 exempt, and "Living Room TV", "brother printer" and "appletv-basement" on an unknown MAC all stay eligible.
Identity got probed rather than assumed, too — the discipline the iPhone roll-call earned the hard way. The room-named speakers looked like an obvious brand from their names; the port they should have answered on was closed, and the appliance’s matching port returned a 404 that would have mislabelled it. AirPlay reported the real models — a HomePod and a Mac — and those went in as exact-MAC pins instead of blanket vendor prefixes, because a vendor prefix that catches a console is a hole.
The first night caught one anyway
Section titled “The first night caught one anyway”The fix went in before curfew and the sweep ran at 23:00. It paused one device: the Brother printer — blocked at 23:00:20, released by the orphan sweep at 23:00:23. Three seconds, and still a miss.
A plain methodology error. The at-risk set was enumerated from the live device view, and that query filtered on online. Printers sleep — so do the bulbs, a speaker’s second radio, the receiver’s other NIC, and a handful of sensors. A whole class of intermittent infrastructure, invisible precisely when you go looking for it and eligible again the moment it wakes inside the window. Twenty-five devices fixed; the real set was seventy-two.
Recomputing from the stored registry, the same table the sweep reads, found the rest. A second lesson sits underneath: the live endpoint classifies using the device’s name, so the printer displayed as printer in every view an operator would check while the sweep, reading a stored unknown, still saw a stranger. Dashboard and enforcement path disagreed, and only one was armed.
It also priced the MAC-only decision. Refusing device-supplied names closes the rename hole, and means a printer can never be rescued by looking like one: anything categorised by name must be carried in config, by MAC. The printer is what that costs.
| Before | After 08-11 | After 08-12 | |
|---|---|---|---|
| Sweep-eligible records | 72 | 60 | 59, none of them infrastructure |
| Paused at 23:00 | 25 | 1 | 0 expected |
What remains eligible is what should be: thirty-odd rotating-MAC watches, sixteen unnamed private MACs, a few personal laptops. Randomized MACs cannot be pinned, so whether a parent’s watch belongs in a child’s curfew sweep stays an open question for Bert rather than one quietly answered by an exemption list.
What it cost
Section titled “What it cost”Ninety-two hours of a false outage. Four days of Bert being told to run a destructive command. An unknown number of nights where the house briefly cut off its own hub and thermostat, alerted him each time, and was rescued by a safety net nobody knew was carrying it — every event reported, accurately and uselessly, as unreviewed device(s) paused.
Three claims here arrived confident and wrong, and each died to the same question: what does the disk actually say? The guardian’s “unreachable” died to one curl. The exemption list’s “twenty-five devices” died to a registry query that counted the sleeping ones. The ten-hour blackout — written first on this page — died to four more lines of log, where the release had been sitting fourteen seconds after the pause the whole time.
A monitor that cannot tell its own blind spot from a fault will invent an outage. So will an engineer.
| Gate | Evidence |
|---|---|
| Everything E2E Tested | Screen-time suite 882 passed, 0 failed. Token gate pinned by a 5-case permission matrix; the new credential branch executed for real — leaked-mode tokens each yield exit 4 and leave the offline counter at 0. Guardian live: counter 374 → 0, all-clear broadcast. Curfew eligibility called against the real registry, offline records included, rename-evasion and random-MAC controls held eligible. The 23:00 run caught one device — printer, now pinned; 72 → 59 eligible, none infrastructure. SSH paths and the repaired add-on verified, host keys corroborated over two networks |
| in Sanctum-docs | This field note + sidebar entry |
| Merged | openclaw-skills 19fdd40; sanctum-screen-time d3d2517, 7fd4b69; sanctum-config c8272d8, 05862ae — all on origin/main |
| And Deployed | Guardian healthy on its own schedule; enforcement service restarted twice behind the secrets-perms guard and verified armed both times; terminal add-ons started with supervisor watchdogs now enabled on both |
The sentinel is still at the door, still allowed to wake anyone in the house. It just has to be able to read its own key before it decides the house is on fire.