Showing "this session is 34% full" requires two numbers. The token count is easy and always right. The window size it divides by is not in the transcript, not in the session state files, not in settings, and not in any hook payload. It exists in exactly one place, and if you cannot read that place you are guessing at the denominator of a number you are about to display as fact. 🧪 Tested with Claude Code 2.1.x · macOS I built a script that reports how full a Claude Code session’s context window is, so a Stream Deck key could draw a ring for it. The token side was straightforward. The denominator turned into the whole project.
`launchctl bootout` exited 0. `launchctl bootstrap`, on the very next line, failed with `Bootstrap failed: 5: Input/output error` because the label was still loaded. Then the bootout finished on its own schedule and the label went away, leaving a machine with no daemon at all and an installer that had just told me it was done. This one is mine: I caused it while fixing something else. 🧪 Tested with launchd · macOS I have written before about launchctl unload returning 0 while the daemon kept running, which turned out to be KeepAlive’s supervisor racing the teardown. This is the sibling. Same API, opposite failure, and where that one was launchd surprising me, this one I built myself.
The daemon reported the tunnel as established. The local socket file was right there on disk with the right permissions. The target sat at `offline` forever. Meanwhile `connect to /home/nick/.config/herdr/sessions/main/herdr.sock port 0 failed` was being written to a stderr stream that nothing in my process ever read, several times a second, for as long as I left it running. Deleting ClaudeDeck · Part 5 of 5 1 2 3 4 5 🧪 Tested with herdr 0.8.0 · macOS HerdDeck drives agents on a second machine. My laptop has the Stream Deck and the daemon; the desktop runs herdr and the agents actually doing work. They are joined by ssh -N -L <local.sock>:<remote.sock> host, a Unix-domain socket forward, and the daemon speaks the same protocol to a forwarded socket as it does to a local one.
A key on my Stream Deck showed an agent with a context window 65% full. The ring around it displayed a little over a third of a circle, which anyone would read as 35%. Not blank, not obviously broken, no missing-data placeholder. Confidently wrong, by exactly the amount that inverts the decision you are making when you look at it. Deleting ClaudeDeck · Part 4 of 5 1 2 3 4 5 🧪 Tested with herdr 0.8.0 · macOS That is the hardware, live against five agents on a remote machine. The top row is one key per agent. The tint is that session’s lifecycle state, and the ring is how full its context window is. Two independent signals, one key.
Each agent key on the Stream Deck draws a ring showing how full that session's context window is. Mine sat at 10% for days. Every session, regardless of activity, regardless of how long the agent had been grinding. I found the bug, fixed it, watched agents start reporting correct percentages every single turn, and the ring still said 10%. That was the good part, because it meant there were two. Deleting ClaudeDeck · Part 3 of 5 1 2 3 4 5 🧪 Tested with Claude Code 2.1.x · macOS Ten percent is a suspicious number. Not zero, which would say “nothing ever arrived”. Not a plausible-looking 37%, which would say “this works and your session is small”. Ten percent is round, and round numbers in a display that should be noisy mean the display is not reading anything.
The daemon's log contained the line `plan poller cadence: errors=1` four hundred and ninety-two times, and `errors=4` five times. A counter that is supposed to climb during an outage had spent its entire life bouncing off one. A third of my requests were being rate-limited and the backoff built to handle that never engaged once, because it was answering a question nobody had asked. Deleting ClaudeDeck · Part 2 of 5 1 2 3 4 5 🧪 Tested with herdr 0.8.0 · macOS The Plan Usage key on the Stream Deck renders my Claude plan’s 5-hour and 7-day windows with a countdown to the next reset. HerdDeck polls Anthropic’s OAuth usage endpoint to fill it. That endpoint is undocumented, which is a story I have already told on this blog and will be correcting later in this post.
Six of the ten posts in my "Building ClaudeDeck" series document code that no longer exists. Over four days in August I rebuilt the plugin on herdr's socket API, and the hook dispatcher, the PTY runner, the statusline auto-patcher, the AppleScript focus path, the Claude project watcher and the shell-PID resolver all went in the bin, taking the `.app` bundle, the codesigning step and every TCC prompt with them. Then the substrate sent its invoice. Deleting ClaudeDeck · Part 1 of 5 1 2 3 4 5 🧪 Tested with herdr 0.8.0 · macOS ClaudeDeck was complicated for exactly one reason. Claude Code has no control API, so every fact the plugin needed had to be stolen from somewhere it was not offered.