A blocked Claude Code session looks exactly like a busy one

A notch HUD for Claude Code. What it costs to watch a session from outside, and what I refused to put on screen.Four Claude Code sessions drawn as islands.I didn’t plan to run four Claude Code sessions at once. It happened the way these things do. One session per worktree, because watching one work…

A notch HUD for Claude Code. What it costs to watch a session from outside, and what I refused to put on screen.Four Claude Code sessions drawn as islands.I didn’t plan to run four Claude Code sessions at once. It happened the way these things do. One session per worktree, because watching one work while another sat idle felt like a waste. Within a week it was just how I worked. Within two, the process changed.Eleven minutes of nothingFour terminal tabs. Four Claude Code sessions. Three repos.Something needs me. I can’t tell which one.Tab one is mid-Grep and wants nothing. Tab two finished four minutes ago and has been sitting on the result. Tab three has been parked on a permission prompt for eleven minutes, because the last time I checked, it wasn’t asking yet. Tab four is working.Fifteen minutes of agent time waiting on me, and ninety seconds of hunting to find that out. All day. Not a crisis. A tax.Four things go wrong, and they compound.A blocked session looks exactly like a busy one. From outside, both are a terminal that isn’t moving.Sessions are unidentifiable from outside. Five worktrees of one repo is five tabs: same tool, same font, same scrolling output. I have answered a prompt in the wrong tab, which is worse than not answering it.Spend is invisible. Context filling, the usage window draining. Either would change what I did next. Instead, you learn where the limit is by hitting it.And checking costs a switch. Going to look means leaving my own work and coming back to find my place gone. So the rational move is to check less often, which makes every gap longer.tmux panes, iTerm tabs, a second monitor of terminals. They all show output, and output is not state. claude agents gets closer, but it reports an interactive session as idle or busy, and either way you have to go and ask.This is a monitoring problem. Several processes, each holding state I need and cannot see. The answer isn’t a dashboard you remember to check. It is a display that is always visible and never asks anything of you.Why the notchThree criteria: glanceable, always on, zero interaction. If I have to click to find out whether anything needs me, it has failed, because clicking was the cost.A window gets covered, so it isn’t always on. A menu bar icon is one colour at best, which isn’t glanceable. A notification you dismiss, and it reports the past. The notch passes all three. No window ever covers it, and it is already dead space.Dead space turned out to be the wrong way to think about it, and this was the most expensive lesson in the project. The notch is a hole, not a region. Those pixels do not exist, and nothing clips your content for you. My first version centred a pill on the cutout, and the text in the middle simply vanished, disappearing at x=790 and reappearing at 1010.It looks exactly like a text-truncation bug. I fixed it twice, wrongly, before I thought to measure the gap and found the real edge at 790.5. In my defence, nothing in the API surface says those pixels are simply absent.So the island flanks the camera. Content sits either side of the cutout with nothing in the middle, which is how it ends up as one line saying which session it is and what it is doing. Nothing else.Nothing publishes the state I neededNo API answers “is this session blocked?” Claude Code reports what just happened, not what is true now, so the state I wanted has to be derived from a stream of events and remembered between them.There are four places to look. Hooks are shell commands Claude Code runs on lifecycle events, and they are the only source that fires the moment something changes. I wired eleven of them, SessionStart through SessionEnd. Because hooks live in your global settings, every session on the machine announces itself, including the ones I didn’t start.The other three are colder. The transcript is JSONL on disk, holding what hooks omit: tokens, model, branch, plan progress. The status line hands JSON to a script several times a second, and is the only place the exact context window is ever stated. OpenTelemetry ships aggregate metrics to a collector, which is the right shape for what a team spent last week and the wrong shape for which session is blocked right now, so I skipped it.The three I kept each lie in their own way, and learning how took longer than wiring them up. There is no event for the assistant beginning to respond, so thinking is inferred from the absence of a tool call rather than observed. The transcript repeats one usage object across every content block of a response, so summing it naively overcounts output by roughly 2x.A /clear rewrites that file from the top, silently invalidating the byte offset you were reading from. And a session that started before the hooks existed emits nothing at all until it restarts, which is the one gap I can't paper over.Then the constraint that shaped everything else. Watching has to be free, because hooks run synchronously and hold the session while they run. So the client that ships each payload links Darwin and nothing else, budgets 50 ms to connect, and exits silently rather than make Claude wait.It runs in about 2 ms. Nothing polls. With four sessions live and the island animating, the app sits under 1% of a core; with no sessions at all, it measures 0.000%, because nothing is left scheduled.What survives is one pure function from (event, session) to (session, timed follow-ups), with no clock, no socket, and no UI, so the whole transition table is testable without any of them.Does anything need me?That is the only question the resting island has to answer, and it has to answer it without being touched.So every state owns a colour before it owns a word. Working is blue, thinking indigo, compacting violet, done mint, failed rose. Amber means I owe something, either a permission prompt that has stopped work or a session sitting idle on my next prompt. After a day, you stop reading the word and read the colour, which is the point.Amber outranks everything, including recency. A session waiting on a permission keeps the island even when another worktree is more recently active, because otherwise the prompt disappears exactly when it matters.What it leaves out matters as much. No tool output, no diffs, no conversation, no cumulative token counts. A HUD that shows everything is just a small terminal, and I already had four of those. Where a number would be a guess, it draws nothing at all, because a zero reads as good news.Three sessions stay separable at a glance: each row carries its title, its branch, and a rail in its state’s colour, so you find the amber one before reading a word.Six is where it breaks. Four rows fit, the fifth peeks under a fade, the rest are behind a scroll, and past four the glance has already degraded into scanning. I don’t have a good answer for that yet, and I’m not sure there is one that isn’t just a list.Answering without going thereKnowing a session is blocked still leaves the switch to make, and that was the cost that hurt most. It is also what turned this from a display into something else.PermissionRequest is a decision hook. Print a JSON decision to stdout, and Claude Code takes it as the answer. The HUD already held the exact tool and the exact input, so the only thing missing was two buttons.It races the terminal rather than replacing it. Claude Code paints its own dialog and waits on the hook at the same time, so whichever is answered first wins. Every failure lands in the same place. A dead HUD fails to connect in 50 ms, the dialog stays where it was, and you walk back to the terminal, which is where you started.It also declines. It won’t offer Allow for a command it cannot show whole, because approving something with an ellipsis through the middle is a hazard the terminal doesn’t have. It won’t answer when two prompts are live in one session, since parallel tool calls raise one each while the terminal shows one at a time, and a press could approve the call you are not reading.There may be a way to pair them up that I haven’t found. And it won’t write a persistent always-allow rule, because that shape isn’t verified to the standard that Allow and Deny are, so approving the same command for the fortieth time still happens.Will it run on my machinemacOS 14 or later. A notch is not required, though. On a display without a cutout, an older Air, or an external monitor, the island becomes a pill just below the top edge.Pick a display by name in Settings; unplug it, and the HUD falls back to the menu bar’s display until yours returns, which is also what clamshell looks like. It joins every Space as a full-screen auxiliary panel, so full-screen apps don’t hide it. Intel Macs work too; they just take longer to build.What it readsThe transcripts it tails contain my source code and my prompts. The hook payloads contain every command Claude proposes to run. That is the sensitivity level. All of it stays on this machine. Payloads travel over a Unix socket with owner-only permissions, and the app makes no network calls, ever.No telemetry, no analytics, no update check; the only network activity in the whole project is the installer’s git clone. Secrets are stripped before any string reaches the UI, and truncated only after stripping, so a cut cannot leave half a key on screen.Try itcurl -fsSL https://raw.githubusercontent.com/vishwam-chepuri/claude-island/main/Scripts/install.sh | bashIt clones, builds, and installs in about forty seconds on Apple Silicon, and it is one file if you want to read it before piping it anywhere. Say yes when it offers to wire up the hooks; it backs up ~/.claude/settings.json first and leaves other tools' entries alone.Homebrew works too, in two commands, the second because Homebrew’s sandbox can’t write to /Applications:brew install vishwam-chepuri/tap/claude-islandclaude-island-installRe-run that second one after everybrew upgrade, or the old version quietly stays installed.Then restart any Claude Code sessions already open. Hooks are read at session start, so an older session sends nothing and looks broken when it isn’t.Both paths compile on your machine. There is no Developer ID here, so a downloaded app would arrive quarantined, and locally built code never is. Expect a build, not a download.Visibility, not supervisionAgentic coding did not remove the need to supervise the work. It removed the ability to see it. One session in one terminal was its own status display. The output scrolled past, and you knew where you stood. Five sessions across three projects hold just as much state and show you none of it.So this is a small app with a narrow claim, built by the same kind of sessions it now watches. It doesn’t make the agents faster. They were never the slow part. It makes them legible, from across the room, without being asked.The code is at github.com/vishwam-chepuri/claude-island, and given the sample size, the issues are the fastest way to tell me it doesn’t work on your machine.This story is published on Generative AI. Connect with us on LinkedIn and follow Zeniteq to stay in the loop with the latest AI stories.Subscribe to our newsletter and YouTube channel to stay updated with the latest news and updates on generative AI. Let’s shape the future of AI together!A blocked Claude Code session looks exactly like a busy one was originally published in Generative AI on Medium, where people are continuing the conversation by highlighting and responding to this story.

Source: Generative AI Pub — Published — Category: Image AI

🔗 Read full article on Generative AI Pub →