The last agent to finish puts my computer to sleep

I work with one Claude Code session per project. Each runs in its own terminal, on its own workspace (a virtual desktop, one keystroke away), and on a busy day there are five of them going at once. One is running a test suite, one is waiting for me to approve a command, one finished ten minutes ago and has been sitting there politely ever since. Which one? Until a few days ago I found out the slow way: tab through all five and read.

Then I listened to DHH on Lex Fridman's podcast, and he mentioned Herdr. Herdr is a terminal multiplexer built for AI coding agents (a multiplexer runs several terminal sessions inside one window), and it ships with Omarchy, DHH's Linux setup. Its sidebar shows per agent whether it is working, blocked, done or idle, and it can play a sound or show a notification when that changes. I wanted the first half of that and very much not the second.

A flag, not a doorbell

Think of a doorbell and one of those mailbox flags you see in American front yards. Both tell you there's something for you, but only the doorbell decides when you find out. With five agents running, a sound per finished agent is a doorbell that rings every few minutes all day long, and every ring costs me whatever I was holding in my head at that moment. So the requirement for this build was one line: inform me, never interrupt me.

The place I already look is the status bar of my window manager. I use i3 (a tiling window manager for Linux, here in the Regolith flavor), and its bar shows a button per workspace. That's where the state went. Each button that holds a Claude Code session now shows a short project label and an icon:

IconStateRed?Meaning
gearworkingnothe agent is busy
hourglasswaitingyes, until I lookit asked me something, needs a permission, or ended its answer with a question
checkdoneyes, until I lookit finished and is idle
clockbackgroundnoits turn is over, but a job it started is still running, and it will wake up by itself when that job reports back
The workspace buttons in the status bar: 1, then 3 with a gear and the label Docs, 4 with a clock and Onno-blog, 5 with a gear and Phone, 6 with a red dot, 10 with a check and Projects. Further right, a moon icon and the words sleep when done.
My bar on the evening this was written. Two sessions working (gear), this one waiting on a background job (clock), one done and already seen (check), and at the right, sleep-when-done armed.

The red is not something I drew. X (the display system underneath i3) has an "urgency hint" a window can raise, and i3 already colors a workspace red when a window on it does. It clears the moment that terminal gets focus. The icon stays until the state changes, so a check I've already seen is still there, just no longer red. If two sessions share a workspace, the most urgent one wins the button. Nothing beeps, nothing pops up, and nothing ever takes focus.

How it works, in two paragraphs

Claude Code has hooks: commands it runs at fixed moments in a session's life, such as the start, the end of a turn, a question, a tool call, the end of the session. One small Python script listens to all of them. It writes the session's state to a file, renames the workspace to label plus icon, and sets or clears the urgency hint. What the hooks don't tell you directly, it reads from the session's transcript (the log file Claude Code keeps of the conversation): whether the last paragraph ends with a question mark, and whether a background job the session launched has reported back yet.

Two things no hook can do: notice that I've looked at a session, and put the red back after i3 restarts. For those there's a status-bar block that runs every two seconds and prints nothing at all. Everything lives in a git repository and is symlinked into place, like the rest of my machine's configuration. It was built over two days, October 4 and 5, with Claude Code doing the typing.

Eight ways the bar lied to me

The first version worked in the sense that the tests passed. Then I used it for a day, and the bar told me things that weren't true. Here they are, in the order they turned up:

  1. The button that vanished. Regolith names its workspaces with markup: a colored dot is a <span foreground="#3584E4"> inside the name. When a session ended, the script put the original name back without escaping its quotation marks, so i3 cut it off at the first quote and kept 10:<span> </span>10 <span foreground=. That's broken markup, and the bar draws broken markup as nothing. My report was "the 10 is no longer showing". The funny part: the test had printed the truncated name, and the session had shortened its own output and skipped right over it. The test now compares the restored name character for character.
  2. "public_html". A session started in a web root was labeled after the folder. Generic folder names (public_html, src, codebase) are now skipped in favor of the first parent with a real name.
  3. Two names for one project. A session started with one spelling of a project label and then wrote a project file with a hyphenated spelling. The helper that opens a project's browser window looked for the hyphenated name, found no session, and put the browser on the wrong workspace. Sessions are now matched by directory, not by label.
  4. The VS Code rule that had never worked. Opening a project sent VS Code to the wrong workspace, and the cause was two old mistakes in my own i3 config. VS Code reports its window class as code, and the rule said Code. And assign ... 2 targets a workspace named "2", not workspace number 2 (which also explained why some workspaces never got their colored dot). Years of config, quietly wrong, and I had never noticed.
  5. Done, but not really: background jobs. A session showed a red check while it was waiting for a test run it had sent to the background. Claude Code reports "turn finished" either way. That's where the fourth state, the clock, came from: a job was launched and hasn't reported back.
  6. Done, but not really: stopped jobs. Checking that rule against forty real transcripts found a session still "running" a job from seven hours earlier. It had been stopped on purpose, and a stopped job never sends a completion notice. A stop now counts as an end.
  7. Done, but not really: the watcher. A session watching a long upgrade rehearsal on a server, through a log monitor, showed a red check. A monitor reports every event, so an event can't count as the end, and an expired monitor sends yet another kind of notice. On top of that, the transcript stores line breaks as the two characters \n, which defeated the pattern until it allowed for both. This one was found by replaying the real transcript, not by any test case we'd thought of.
  8. Idle, shown as working. A resumed session gets a new session id, so it looked brand new, and new sessions started out as "working". A session that has just started or resumed is now idle: a check, already seen, not red.

Notice who found most of these. Not the tests. The tests checked that the code did what it was written to do; I noticed when the bar didn't match what I saw on screen. Three of the eight are about one word, "done", which is the one thing the agent itself reports and the one thing I most needed to be true.

For the record, the final check against transcripts: the last 40 sessions contained 72 background launches (46 shell commands, 26 sub-agents), every one was recognized, every finished session read as finished, and the two sessions with jobs that were genuinely still running were found.

The last one out turns off the lights

Once the bar knew reliably when a session was done, a second use followed almost by itself. I often end the day with a few long sessions still going, and I used to tell one of them "put the computer to sleep when you're done". With several sessions running, that's a gamble: the one I asked finishes first and cuts off the rest.

Now there's sleep-when-done. Arm it once and the machine suspends (or powers off, if you ask) when no session has been working or waiting on a background job for two minutes. It checks every 30 seconds. Sessions that are waiting for my input don't hold it back, because they'll still be waiting tomorrow. A moon in the bar shows it's armed, and it runs as its own systemd user service (a background service that belongs to my login rather than to any terminal), so the session that armed it can be closed.

Does this exist already? Partly. Claude AutoShutdown does it on Windows for all Claude Code sessions and their sub-agents, and there's a Claude Code skill called Shutdown When Done. I found nothing comparable for Linux, nor in Herdr or Omarchy, whose power management is time-based.

What I gave up by not using Herdr

Herdr uses the same Claude Code hooks, so the raw information is the same. Its integration is maintained by other people, though, and mine depends on details of Claude Code's transcript format, which changed my detection twice in one afternoon. A Claude Code update can break it quietly, and the first sign will be a check mark that isn't true. On the other hand, Herdr's state lives inside its own terminal window, so you only see it while you're looking at Herdr. Mine is in the one strip of screen that's always visible, whatever I'm doing.

In short

The useful bit of agent tooling wasn't the ping. It was the state, put where I already look, and colored red only when it means something. Making "done" actually mean done took most of the two days, and the bugs only showed up once I was using it for real work. So if you build something like this, use it for a day before you trust it. The tests will tell you it works long before it does.

The flag on the mailbox goes up, and I look when I'm ready. And when the last agent is finished, it turns off the lights.

Tools: Claude Code and its hooks, i3 and Regolith, Python, systemd. Herdr is at github.com/ogulcancelik/herdr.