Version 1.0 Β· macOS only (uses AppKit + launchd
; there is no Windows or Linux build).
A full-width red bar along one edge of every screen that tells you, from across the room, what Claude Code is doing. It sits on the top edge by default (position, thickness, and color are all changeable β see Configuration).
Solid red barβ at least one session is busy (Claude working).** Pulsing red bar**β a session** needs a response from younow (a permission prompt, or Claude blocked waiting for input). No bar**β a turn simply finished, or nothing running.
Busy wins: if any session is still working the bar is solid; it pulses only when one has an active prompt for you (a permission request or an explicit question). A turn that just finishes clears the bar. The bar is click-through (it never intercepts your mouse) and floats above everything, including fullscreen apps, on every Space and every monitor.
Note: pulsing is driven by Claude Code's
Notification
hook, which fires for a real prompt (a permission request, etc.). The ~60sidlenotification is deliberately ignored (hook.sh
filters the "waiting for your input" message), so the bar never blinks just because a finished session is sitting there. One consequence: Claude Code has no distinct event for "ended a turn with a question," so a plain conversational question at the end of a turn reads as done (no bar) β only actual prompts pulse. StructuredAskUserQuestion
promptsdopulse, via aPreToolUse
/PostToolUse
hook on that tool.
Left: solid red along the bottom edge = Claude is working; it pulses when Claude needs a response from you. Right: the menu-bar menu for live configuration.
macOS(uses AppKit +launchd
).Xcode Command Line Toolsβ providesswiftc
to build. Install with:
xcode-select --install
Claude Code CLIβ this is driven byClaude Codehooks. It works with any** locally-runningClaude Code (terminal or IDE extension). It does not**work with cloud sessions (claude.ai/code) β those run remotely and can't reach your local machine.
No other dependencies: no Homebrew, no jq
, no runtime. Just the system Swift
compiler and plutil
(built into macOS).
git clone https://github.com/gregsramblings/claude-status-bar.git
cd claude-status-bar
./install.sh
install.sh
builds the app, installs a run-at-login LaunchAgent (paths derived from wherever you cloned β nothing hardcoded), starts it, then prints a hooks block. Copy that block into
~/.claude/settings.json
(merge it into the top-level JSON
object; if you already have a "hooks"
key, add the four events to it). The block looks like this, with real paths filled in:
"hooks": {
"UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "bash /path/to/claude-status-bar/hook.sh busy" }] }],
"Stop": [{ "hooks": [{ "type": "command", "command": "bash /path/to/claude-status-bar/hook.sh waiting" }] }],
"Notification": [{ "hooks": [{ "type": "command", "command": "bash /path/to/claude-status-bar/hook.sh needs_input" }] }],
"SessionEnd": [{ "hooks": [{ "type": "command", "command": "bash /path/to/claude-status-bar/hook.sh end" }] }]
}
Then start a new Claude Code session and give it something to do β the red bar appears while it works. (Hooks load at session start, so already-open sessions won't drive the bar until restarted.)
echo '{"session_id":"test"}' | ./hook.sh busy # bar should appear
echo '{"session_id":"test"}' | ./hook.sh end # bar should disappear
./uninstall.sh
Stops and removes the LaunchAgent and the state directory. It intentionally does not
edit settings.json
β remove the four ccbar hooks
entries yourself, then delete the repo folder.
- Claude Code
hooks fire on state change and run
hook.sh
, which writes one small JSON file per session into~/.claude/ccbar/state/<session_id>.json
:UserPromptSubmit
βbusy
(you handed off; Claude is working)Stop
βwaiting
(turn finished)Notification
βnotify
, whichhook.sh
splits intoneeds_input
(a real prompt β pulse) orwaiting
(the idle timeout β no bar)PreToolUse
(matchingAskUserQuestion
) βneeds_input
(pulse while a multiple-choice question waits on you);PostToolUse
βbusy
(solid again once you answer)SessionEnd
β deletes the file
ccbar
(a tiny AppKit app, one borderless click-through window per screen at.screenSaver
level, on all Spaces) polls the state directory every 0.4s:solid if any live session isbusy
, elsepulsing if any isneeds_input
, else hidden (waiting
β a finished turn β shows nothing).
Multi-session just works: each session is its own file keyed by session_id
, so
three concurrent sessions share one bar β solid if any is working, pulsing if any
needs your input. Stale files (older than 12h, e.g. from a crash that skipped
SessionEnd
) are ignored.
ccbar adds a small bar-shaped icon to the macOS menu bar. Click it to configure the bar live β no rebuild, no restart:
| Menu item | Default | Meaning |
|---|---|---|
| Position | ||
| Top | Pin the bar to the Top or Bottom edge. | |
| Thickness | ||
6 px |
||
Bar thickness (presets 4 β30 ). Bump for a big room. |
||
| Bar Color⦠| ||
| red | Opens the macOS color picker; recolors the bar live. | |
| About ccbar | ||
| β | Description, author, link to this repo. | |
| Quit ccbar | ||
| β | Stops the app (unloads the LaunchAgent so it stays down). |
Choices persist in UserDefaults
across restarts. The remaining tunables are still
compile-time constants at the top of ccbar.swift
(rebuild + restart after changing β
./install.sh
again does both):
| Constant | Default | Meaning |
|---|---|---|
pollInterval |
||
0.4 |
||
| Seconds between state-dir polls. | ||
staleAfter |
||
43200 |
||
| Ignore state files older than this (seconds). |
| File | Purpose |
|---|---|
ccbar.swift |
|
| The whole app (AppKit, single file). | |
hook.sh |
|
| Claude Code hook target; writes one state file per session. | |
install.sh |
|
| Build + install LaunchAgent + print the hooks block. | |
uninstall.sh |
|
| Stop + remove the LaunchAgent and state dir. | |
ccbar |
|
Compiled binary (gitignored β built by install.sh ). |
Fragility β the idle filter is a string match.hook.sh
distinguishes the ~60s idle notification from a real prompt by matching the substringwaiting for your input
in the notificationmessage
. This is Claude Code's current idle wording, not a stable API. If Anthropic changes that text, the idle timeout will start pulsing the bar again. The fix is one line β update thecase
pattern inhook.sh
. (Conversely, only known idle text is suppressed, so any new message defaults topulse, never to silently hiding a real prompt.)Stop
is the main-agent stop only (SubagentStop
is a separate, unhooked event), so the bar tracks real turn boundaries, not sub-agent churn.- The bar defaults to the top edge. Switch Position β Bottom(or the reverse) and bump** Thickness**in the menu-bar menu to taste β e.g. move it off an edge where a bottom Dock or the notch would hide it.
Public domain β The Unlicense. Do whatever you want with it, no attribution required, no warranty, no liability.