One glanceable widget for every Claude Code session you have running.
Running one Claude Code session is easy to follow. Running five is not.
The moment you go parallel, the useful question stops being "what is Claude doing?" and becomes "which of these is stuck waiting on me?" — and the answer is invisible. A session blocked on a permission prompt looks exactly like a session busy thinking: both are just another terminal behind another window. So you alt-tab through all of them, find the one showing a prompt, answer it, and discover a second one has been idle for six minutes.
claude-fleet
puts all of them in one small always-on-top strip, sorted so the one that needs you is always on top.
Each session is one row: a coloured dot, the repository name, and the branch narrowed to its two
significant words (web-client :dark-mode
). Rows are sorted by urgency, not by name.
| State | Colour | Meaning |
|---|---|---|
PERMISSION |
||
| red | Claude is blocked asking to run something | |
WAITING |
||
| amber | A notification is waiting on you | |
RUNNING |
||
| green | Working — nothing needed from you | |
DONE |
||
| grey | Finished or idle |
When a session raises a permission prompt it jumps to the top, turns red, the window pulses once, and the prompt text appears beneath the row. When every session is done, the widget dims and gets out of your way.
Three things the demo above cannot show, because the capture records only the widget itself:
Left-click a row to open that project in VS Code. If a window is already open on that folder it is focused rather than duplicated.Right-click a row forOpen in VS Code, plusHide sessionon finished rows.Hover a row for a tooltip with the full working directory, elapsed time, and either the full notification text or the prompt the session was given.
The widget is pinned across virtual desktops, hidden from alt-tab, and Windows toasts fire on notifications, so a session on another desktop still reaches you.
- Windows 10 or 11 (the widget, the toasts and the hook script are all Windows-specific)
- Python 3.11 or newer (
tomllib
) - Claude Code
- VS Code, optional — only for click-to-focus
Clone and install in editable mode. This is the supported install path: the hook script has to exist on disk for Claude Code to invoke it, and it lives in the repository rather than inside the Python package.
git clone https://github.com/shalom-borohov/claude-fleet.git
cd claude-fleet
py -3 -m venv .venv
.venv\Scripts\pip install -e ".[dev]"
Check it runs:
.venv\Scripts\fleet status
It will print No active agents.
until the hooks are wired up.
claude-fleet
has no daemon and does not watch Claude Code. Claude Code pushes events to it through hooks, so nothing appears until you register them.
.venv\Scripts\fleet print-hooks
That prints a ready-made JSON block covering six events — SessionStart
, UserPromptSubmit
,
PostToolUse
, Notification
, Stop
and SessionEnd
— each pointing at the absolute path of your
clone's hooks/record_event.ps1
. Merge it into the "hooks"
object of ~/.claude/settings.json
:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "& 'C:\\path\\to\\claude-fleet\\hooks\\record_event.ps1'",
"shell": "powershell",
"timeout": 10
}
]
}
]
}
}
TheIf you already use one, merge the two commands into a single entry rather than overwriting it.Notification
entry replaces any existingNotification
hook you have.
Start a Claude Code session, then run the widget:
.venv\Scripts\fleet watch
| Command | Does |
|---|---|
fleet watch |
|
| The always-on-top widget | |
fleet status |
|
| Prints current state as text, no GUI | |
fleet print-hooks |
|
Prints the settings.json block to merge |
$ fleet status
PERMISSION web-client:dark-mode 14s Claude needs your permission to run npm publish
RUNNING payments-api:stripe-webhook 92s
DONE infra:terraform-drift 431s
Drag the header to move the widget; it snaps to screen edges and remembers where you left it.
Press Esc
or click ✕
to close it.
Optional TOML at %LOCALAPPDATA%\claude-fleet\config.toml
. Unknown keys are ignored.
poll_interval_sec = 1.5
stale_after_minutes = 480
exclude_projects = ["scratch", "dotfiles"]
| Key | Default | Meaning |
|---|---|---|
poll_interval_sec |
||
1.5 |
||
| How often the widget re-reads session state | ||
stale_after_minutes |
||
480 |
||
| Records older than this stop being displayed | ||
data_dir |
||
%LOCALAPPDATA%\claude-fleet |
||
| Where session state and widget position live | ||
exclude_projects |
||
[] |
||
| Folder names to never display |
exclude_projects
matches the last path segment of the session's working directory — the folder Claude Code was launched in, not the repository name shown on the row.
The two halves never call each other; they are decoupled through the filesystem.
Claude Code ──hook event (JSON on stdin)──> record_event.ps1
│
writes one JSON file per session
│
%LOCALAPPDATA%\claude-fleet\sessions\
│
polled every 1.5s
│
fleet watch / status
Each event overwrites that session's file, so what is on disk is always the current state rather
than a history. SessionEnd
deletes it. Sessions that crash without firing SessionEnd
simply age
out once they pass stale_after_minutes
.
CLAUDE.md documents the design in depth — the state machine, why the branch label is narrowed to two words, and why clicking opens a matched ancestor folder instead of the raw working directory.
The widget is empty. Hooks are not registered, or every session is stale. Run fleet status
to
check without the GUI, and confirm the path in ~/.claude/settings.json
points at a
record_event.ps1
that actually exists.
A session is missing. Its record is older than stale_after_minutes
, or its folder name is in
exclude_projects
, or you hid it from the right-click menu. Hidden sessions come back on their own as soon as the session does anything again.
No toasts. Toasts only fire on Notification
events. Check Windows notification settings, and
make sure the Notification
hook was not overwritten by another tool.
Clicking a row opens a second VS Code window. Windows are matched by folder name off the VS Code window title, so two open windows whose folders share a leaf name are indistinguishable, and a folder that is not currently open cannot be matched at all.
Rows for sessions that already ended. State is only written when a hook fires. A killed terminal
never sends SessionEnd
, so its row stays until it goes stale.
.venv\Scripts\python -m pytest
Tests live in test/unit/
. To re-record the demo after a UI change:
.venv\Scripts\pip install -e ".[demo]"
.venv\Scripts\python tools\capture_demo.py
That drives the real widget with synthetic sessions and writes docs/demo.gif
. It moves the mouse pointer aside and shows a window for about twelve seconds while it records.
This is an unofficial community project. It is not affiliated with, endorsed by, or supported by Anthropic.