Project: Soft Rains Entry 10

The news that played to nobody

Three layers of the morning routine each reported success. None of them were doing their job.

The morning routine is meant to be simple. When the house decides people are up, the Apple TV starts a newscast, the Sonos Arc comes down to fifteen percent so it is not shouting, and the lights go to something that does not hurt. I have had some version of this running for a while. Over about four days it failed in three completely different ways, and each one was hiding behind the one in front of it.

Context that makes the whole week worse. I was away for most of it, and a friend was staying to look after the place, so every one of these failures happened to somebody else while I watched it from a laptop. I was screen sharing into the Mac mini across my Tailscale network, which worked flawlessly and which I would recommend to anybody running something like this, with the caveat that it puts you in the worst possible position: complete visibility, complete control, and absolutely no ability to walk into the next room and look at the television.

One thing gave me no trouble at all the entire time I was gone, so it is worth saying plainly. The front door is a Schlage BE889WB, it runs inside HomeKit exactly as I set it up months ago and nowhere near any of my own code, and it has been an absolute dream. It let my guest in, it locked itself behind them every time, and it did not once require my attention. The lesson I keep relearning is sitting right there in the contrast, and I will come back to it at the end.

The launch that returned success and did nothing

tvOS updated to 26.6 and the newscast stopped starting. Nothing in my logs said so. The command I use to launch an app on the Apple TV runs over the Companion protocol, and it returned cleanly every single morning, exit code zero, no error, no warning. The app simply never opened. A success code straight into the void.

I moved the launch over to devicectl instead, which actually opens the app, and that would be the whole story except for what happened next.

The verification that lied in the other direction

Having been burned by a launch that claimed success, I added a check: after firing, ask the Apple TV what it is doing and confirm it is playing. On tvOS 26.6 the Companion protocol reports Idle while the app is visibly playing on the screen. So the check failed every morning, decided the launch had not taken, and relaunched. Which restarted the newscast from the top, on top of itself, while I was standing there watching it play perfectly well.

So now I verify by asking the device for its running process list instead, which is slower and uglier and has the notable advantage of being true. There is a lesson in there about trusting the thing that reports state versus the thing that has state, and I have now learned it twice in one week from the same device.

Two smaller ones from the same pass, both embarrassing. The routine now waits fifteen seconds after waking the TV and sends a select click first, because the profile picker that appears after sleep silently swallows the launch. And it launches with the flags that force a fresh start, because otherwise the newscast resumes wherever it was left rather than starting at the top, which is a strange way to hear the news.

The part that was actually my fault

All of that was the house failing to do a thing it was told. The real problem was that it kept being told at the wrong time.

The original trigger was motion in the kitchen. It fired one morning at six, off nothing, and started a newscast at volume in an empty living room with my house guest asleep on the floor directly underneath it. There is no good way to read that message when you are nowhere near the house and cannot do a thing about it. So I tightened it: require motion in the kitchen and the living room within fifteen minutes of each other. Two rooms, surely.

It fired again at 06:03. Two motion trips, each about ten seconds long, one in each room, everybody still asleep. Turns out requiring two rooms just means waiting slightly longer for two pieces of noise.

At which point I stopped guessing. I had seven mornings of recorded sensor data sitting in a log file, so I wrote a small replay tool that takes a proposed rule and tells me what it would have done on each of those seven mornings. Then I tried thresholds until I found one that fired on every morning somebody was actually up and on none of the ones they were not. The answer was sustained motion: at least three trips inside the rolling window, not just presence, not just two rooms.

Then I went further, because the replay made the shape of the problem obvious. The phantom trips were all coming from the convenience motion zones, and the real ones always had a proper passive infrared sensor or a door event in among them. So the gate now requires corroboration from one of those trustworthy classes before it will believe anybody is awake. Replayed against the recordings, the ghost mornings get rejected and the real ones all survive.

What I actually took from it

I spent four days on this and wrote maybe thirty lines of logic that matter. Every single failure was a layer telling me it had done its job. The launcher said it launched. The verifier said it was idle. The motion sensor said there was motion. None of them were lying exactly, they were all answering a slightly different question from the one I thought I was asking.

The replay tool is the bit I would keep if I had to throw the rest away. Picking a threshold by arguing with myself in the shower was how I got the first two rules, and both of them were wrong within a day. Picking it by running the candidate against a week of what actually happened took twenty minutes and has held.

And then the lock, which I wrote a rule about at the very start of this project and have not thought about since, quietly did its job every day of the same week without me. That is not a coincidence and it is not luck. It is that the door is running software written by people who tested it properly, and the newscast is running software written by me on weeknights. I am not going to stop writing the weeknight software. I am getting better at being honest about which category a thing belongs in before I put it in charge of something.