GitHub - Shalom-Borohov/claude-fleet: Live status widget for monitoring several parallel Claude Code sessions at once, on Windows. Fed by Claude Code hooks.

GitHub

6 min read Original article ↗

One glanceable widget for every Claude Code session you have running.

CI License: MIT Python 3.11+

The claude-fleet widget: three sessions running, one raising a permission prompt and jumping to the top, then all finishing

Why

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.

What you see

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 for Open in VS Code, plus Hide session on 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.

Requirements

  • 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

Install

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.

Wire up the hooks

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
          }
        ]
      }
    ]
  }
}

The Notification entry replaces any existing Notification hook you have. If you already use one, merge the two commands into a single entry rather than overwriting it.

Start a Claude Code session, then run the widget:

.venv\Scripts\fleet watch

Using it

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.

Configuration

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.

How it works

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.

Troubleshooting

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.

Development

.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.

Notes

This is an unofficial community project. It is not affiliated with, endorsed by, or supported by Anthropic.

License

MIT