git-roost
top for git — every repo and worktree on the box, in one table, most
actionable first.
roost answers what are my Claude
sessions doing. git-roost answers the other half: what is actually in the
trees they are working in. Sessions report intent; git reports what happened,
and only one of those two can be wrong.
One file, no dependencies, Python 3.9+, macOS/Linux/Windows.
The short ambient loop below is the same program, watching quietly:
Recorded against a synthetic fixture, not a real machine — see
demo/ for how. Text fallback, same shape:
REPO TREE BRANCH WORK DRIFT STASH LAST SUBJECT
MID-OPERATION
copilot-money-mcp fork-merge-and-repack fork/merge-and-repack 17 ^1 1h ** merge in progress, 3 conflict(s) **
DIVERGED
counting-chicken-wings docs-roadmap-refresh docs/roadmap-refresh clean ^1v1 1 1h Re-verify the roadmap against the tree
llm-security-rules pipeline-e2e pipeline-e2e clean ^4v8 3h Add launch post draft
UNCOMMITTED
llm-security-rules fix-action-injection fix/action-injection 2+1? = 1h fix: script injection via unquoted input
UNPUSHED
copilot-money-mcp fork-merge-and-repack fork/merge-and-repack clean ^1 1h test: registry-derived tool-count check
BEHIND
roost (primary) main clean v2 1h Bump version to 0.3 (#3)
ACTIVE
shunt-ai-power (primary) main clean = 3m docs+fix: wall-power wording
QUIET (8) blog/chicken-fest . copilot-money-mcp/(primary) . repo-security-ci/(primary) . ...
27 tree(s) across 11 repo(s) | 2 with uncommitted work | 1 mid-operation
Install
The distribution, the command, the module and the repo are all the bare
git-roost — pick whichever channel is already on the box.
pipx install git-roost # or: pip install --user git-roost
npm install -g git-roost # if Node is what you have
brew install gmhoward9289-ops/tap/git-roost
Or just take the file. It is one script with no dependencies, so curl and
chmod +x is a complete install:
curl -fsSLO https://raw.githubusercontent.com/gmhoward9289-ops/git-roost/main/git_roost.py
chmod +x git_roost.py && ./git_roost.py
Installed under any of those names, git roost works too: git dispatches an
unknown subcommand to a git-<name> on PATH.
The man page installs to <prefix>/share/man/man1. A system or Homebrew install
puts that on the default MANPATH; a venv or pipx install does not, so man git-roost there needs MANPATH help.
Why
lazygit, gitui and tig are all excellent and all single-repo. They answer
"what is happening in this repo". They do not answer "which of my thirty trees
is diverged, which has uncommitted work nobody has looked at, and which one is
stuck half way through a rebase" — which is the question you have the moment
more than one thing is working in parallel.
Usage
git-roost # one table, most actionable first
git-roost -1 # render once and exit (the default; --once)
git-roost -w # redraw every 3s (the top view)
git-roost --log # commit feed across every repo, newest first
git-roost --all # expand the QUIET group
git-roost --json # records, for piping somewhere else
git-roost --root ~/src # look somewhere other than ~/GitHub (repeatable)
Watch mode takes keys:
| Key | Action |
|---|---|
? |
the keymap |
r |
refresh now |
s |
sort: recent / repo / work |
f |
filter: all / uncommitted / mid-operation |
a |
expand or collapse QUIET |
q |
quit |
Sort cycles within a group and never across one. The group order is the whole
argument this tool makes — cost of ignoring, not recency or size — so a sort
that let an ACTIVE tree float above a MID-OPERATION one would be quietly
answering a different question.
Keys need a terminal. Piped, redirected, or on a box with neither termios nor
msvcrt, watch mode degrades to the plain timer redraw rather than failing, and
the default one-shot render touches no terminal settings at all — which is what
keeps git-roost | less and git-roost --json | jq safe.
Reading the table
Groups are ordered by what it costs to ignore them, not by how interesting they look.
| Group | Meaning |
|---|---|
MID-OPERATION |
Stuck part-way through a rebase, merge, cherry-pick, revert or bisect. This is the one that most needs a human, and it looks identical to an idle tree in every other column. |
DIVERGED |
Ahead and behind. Someone is going to resolve a conflict later. |
UNCOMMITTED |
Tracked changes sitting unsaved. |
UNPUSHED |
Commits that exist only on this machine. |
BEHIND |
Someone else moved on without you. |
ACTIVE |
Committed within the hour, otherwise clean. |
QUIET |
Collapsed to one line. --all expands it. |
Columns:
- WORK —
clean,3tracked changes,+2?untracked,3+2?both. Untracked files get a marker but never a group of their own: they are mostly scratch output, and one tree here carries a dozen permanently. - DRIFT —
=in sync,^2ahead,v3behind,^2v3diverged,-unknown.-means unknown, never in sync. - STASH — stash entries, blank when there are none.
- LAST — age of the last commit.
Finding the baseline
Drift needs something to measure against. The chain is:
- the branch's own upstream, when it has one;
origin/HEAD;- if the repo has exactly one remote, that remote's
HEAD, then itsmainormaster; - otherwise
-.
Steps 1 and 2 are the ones that matter most and are also the easiest to get
wrong in opposite directions. Branches that are never pushed have no upstream,
so an upstream-only check calls every one of them clean. And origin is a
convention, not a guarantee — one repo here has a single remote named deploy,
and an origin-only lookup filed a tree that was nine commits behind under QUIET.
Step 3 stops at a single remote on purpose. With two remotes there is no way to
know which is authoritative, and a confident wrong baseline is worse than -.
Configuration
Two environment variables, both about how hard to push a slow disk rather than about git.
| Variable | Default | What it does |
|---|---|---|
GIT_ROOST_TIMEOUT |
5 |
seconds any single git call may take before it is abandoned |
GIT_ROOST_WORKERS |
12 |
how many trees are scanned in parallel |
The timeout is a ceiling on one call, not on the scan. A tree behind a stalled network mount is dropped rather than allowed to hold the whole table hostage — in a watch loop, one unreachable repo would otherwise stop every other repo from redrawing.
Read-only by construction
Every git invocation goes through one function that refuses anything outside an allowlist of read-only plumbing. It cannot mutate a tree, an index or a ref — not because it happens not to, but because the call raises.
The allowlist is keyed on (subcommand, first argument), not the subcommand
alone, because the subcommand alone does not settle it: stash list reports but
stash pop mutates and stash clear destroys; config --get reads but
config <key> <value> writes; symbolic-ref --short REF reads but
symbolic-ref HEAD REF rewrites HEAD. A subcommand-level allowlist admits all
of those writes, and an earlier version of this file did.
The test suite asserts the policy directly, asserts each of those dangerous
forms is refused, and asserts that reading a repo leaves its status and HEAD
byte-identical.
This matters because the tool is meant to sit in a watch loop over every repo on the machine, unattended.
Performance
A full scan is parallel across trees, and repo-level facts (stashes, remote refs) are computed once per repo rather than once per worktree.
Measured on 27 trees across 11 repos: ~0.68s per redraw, comfortably inside
the default 3s watch interval. Tunable with GIT_ROOST_WORKERS and
GIT_ROOST_TIMEOUT.
The family
Four tools, one shape: single file, no dependencies, stdlib unittest, and a
table ordered by what it costs to ignore.
- roost —
topfor Claude Code: per-session context burn, models, and the subagents a session spawned. - git-roost — this one. The other half of the same question: not what the sessions say they are doing, but what their trees actually contain.
- leghorn — sessions joined to worktrees and real git state, CI, and a commit feed.
- legbar — both lanes on one screen, over one discovery layer.
git-roost is the one that needs no Claude Code, no sessions and no
~/.claude at all. It reads git and nothing else, which is why it is the one
worth running on a box that has never seen an agent.
Tests
python -m unittest discover -s tests
Stdlib unittest, so it runs with nothing installed; pytest collects it
unchanged.
License
Apache-2.0
Release files for git-roost 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| git_roost-0.2.0.tar.gz | 48.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| git_roost-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 74.6 kB
Release files / git_roost-0.2.0.tar.gz
| Download URL | git_roost-0.2.0.tar.gz |
|---|---|
| Size | 48.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a4810e348b269cd08e9374042e2fc1c5085d9e5eab6aa42f89af6cb1f599eb2c
|
|
BLAKE2b-256 checksum How to use checksums |
844c1162a0a43bb9f00af7547bf260f17fff279925dbaa288735ec801aeb17ce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.
Transparency logRelease files / git_roost-0.2.0-py3-none-any.whl
| Download URL | git_roost-0.2.0-py3-none-any.whl |
|---|---|
| Size | 26.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1546bc22f64a1476dcd43463423342ef829b6369c3bada4449bdb5ea4c288470
|
|
BLAKE2b-256 checksum How to use checksums |
cc8702b49320779d1755a84118ec6f4547cc47e68c6b3159d441c9cd2e850fa7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.
Transparency log