Pick git worktrees and GitHub PRs from a single herdr popup
and review them with loopreview (lr)
in a pane herdr reuses.
A keyboard-driven ratatui picker is the single entry
point. It lists the repo's git worktrees immediately and streams open PRs in the
background. Selecting a row opens its diff in lr, running inside one named
herdr pane per tab — each new selection quits lr and relaunches it on the new
target in that same pane rather than spawning more panes. A safe bulk cleanup
of throwaway worktrees (the kind agents produce) is one keypress away.
Everything runs as child processes of four tools you already have: lr, gh,
git, and herdr. The plugin itself has no fzf, jq, or Python dependency
(the sample keybinding uses jq only to resolve the plugin's path), and the
plugin never handles a GitHub token itself.
- Open / switch the live
lrdiff between any of the repo's worktrees, as a branch diff (origin/<base>...) or the plain working-tree diff. - Open a PR with
lr pr <ref>— type a number,#123,owner/repo#123, or a PR URL, or pick one from the streamed list. - One pane per tab: selections reuse a single named
loopreviewpane, swapping its contents in place instead of proliferating panes. - Clean up worktrees safely, with multi-layer guards that refuse to delete anything still in use.
lr(loopreview) — the terminal diff reviewer. Keep it current withlr update(self-updates from GitHub Releases).gh— authenticated (gh auth login). All GitHub access, auth, and rate limiting go throughgh; this plugin never touches a token.git≥ 2.5herdr≥ 0.7 — the terminal multiplexer this plugin runs inside.
A Rust toolchain (≥ 1.97, edition 2024) is optional — needed only to build from source. It is not required to install or run the plugin: the launcher shim downloads a prebuilt binary on first use.
This plugin drives lr, so install loopreview
first if you don't have it:
lr --version || curl -fsSL https://loopkeep.run/lr.sh | shThen install the plugin from a Git host:
herdr plugin install loopkeep/herdr-plugin-loopreviewherdr plugin install only clones the plugin — there is no build step, so it
works on machines without a Rust toolchain. On first run the launcher shim
downloads a prebuilt binary for your platform from GitHub Releases (via gh) and
caches it under bin/.cache/.
For local development, link a checkout and build it yourself:
herdr plugin link . && cargo build --releaseThe shim always prefers a local target/release/herdr-loopreview if present, so
a linked checkout runs your own build; otherwise it falls back to the cached or
freshly downloaded release binary.
The picker is a full-screen TUI and must run inside a popup, which supplies
the interactive terminal it needs and inherits the launching pane's working
directory (so that becomes the target repo). Add a keybinding to your
~/.config/herdr/config.toml — this plugin never edits your config for you:
[[keys.command]]
key = "prefix+l"
type = "popup"
description = "loopreview picker"
width = "80%"
height = "70%"
command = '''PLUGIN_DIR="$(herdr plugin list --json | jq -r '.result.plugins[] | select(.plugin_id=="loopkeep.loopreview") | .plugin_root')" && exec "$PLUGIN_DIR/bin/herdr-loopreview" picker'''Then apply the change (or restart herdr):
herdr server reload-configBind a second key ending in clean instead of picker to open straight into
the cleanup review.
The sample binding uses jq to resolve the plugin's install path at launch
time; if you prefer not to depend on jq, hardcode the path printed by
herdr plugin list as PLUGIN_DIR instead.
Inside the picker:
| Key | Action |
|---|---|
Enter |
Open / switch the highlighted row in the default view |
Ctrl-B |
Open the highlighted worktree in the other view (branch ↔ working tree) |
Tab |
Toggle multi-select on a worktree (used for cleanup) |
Ctrl-D |
Clean up the marked (or highlighted) worktree(s) |
Ctrl-R |
Reload the worktree and PR lists |
Up/Down, Ctrl-P/Ctrl-N |
Move the cursor |
Esc / Ctrl-C |
Quit |
Type to fuzzy-filter the list — q is a query character, so only Esc /
Ctrl-C quit. Worktree matching considers the origin badge, the branch name,
and the worktree's directory name (not the full path, so parent directories in
an absolute path don't cause spurious matches). When the query looks like a PR
reference (123, #123, owner/repo#123, or a PR URL) the picker offers a
dynamic "open this PR" row.
Each worktree row shows an origin badge and status badges:
- Origin:
herdr(~/.herdr/worktrees/…),claude(…/.claude/worktrees/…), ormanual. - Status:
*(currently shown inlr),dirty,ahead N,behind N,in use.
The picker is the default action, but the same binary can be run directly:
bin/herdr-loopreview picker # the interactive TUI (default; needs a terminal)
bin/herdr-loopreview open # headless: open the current worktree's diff
bin/herdr-loopreview clean # open the picker straight into cleanup
bin/herdr-loopreview --help # usage (also `help` / `-h`)
bin/herdr-loopreview --version # print the version (also `-V`)The open action is headless — it opens (or reuses) the tab's loopreview
pane through the plugin and needs no terminal of its own, so it works from
scripts and agents. It prints a one-line result and exits non-zero on failure:
bin/herdr-loopreview open # branch diff of the current worktree (default view)
bin/herdr-loopreview open --view worktree # the plain working-tree diff instead
bin/herdr-loopreview open --pr 123 # a PR (number, #123, owner/repo#123, or URL)
bin/herdr-loopreview open --path <dir> # the worktree containing <dir>, not cwdpicker and clean need an interactive terminal; open, help, and
--version run anywhere.
Optional settings live in config.toml inside the plugin config directory
(herdr plugin config-dir loopkeep.loopreview). The plugin only ever reads
this file:
# Fallback base branch when origin/HEAD cannot be detected.
# Default: main
# base_branch = "main"
# Launch lr watching for changes (auto-reload). Default: true.
# false adds --no-watch.
# watch = false
# Layout mode passed to `lr --mode`. Only "unified" or "split" are honored;
# "auto" or any other value is ignored (lr's own default is used).
# mode = "split"
# Split ratio (0.0–1.0) for the spawned loopreview pane. Default: herdr's split.
# pane_ratio = 0.6
# Which view Enter opens a worktree as: "branch" (default) or "worktree".
# Ctrl-B always opens the other one.
# default_view = "branch"
# Where a fresh loopreview pane opens: "rightmost" (default) splits the
# tab's rightmost pane; "current" splits the pane the popup was opened from.
# pane_placement = "rightmost"
# The lr executable to run (name on PATH or an absolute path). Default: lr.
# lr_bin = "lr"
# How many PRs to request from `gh pr list`. Default: 50.
# pr_limit = 50Per-tab pane tracking is recorded in state.json in the same directory.
The picker reviews the repository at the popup's working directory ($PWD). If
that is not a git repository it reports an error — open the picker from a pane
inside the repo you want to review.
The plugin keeps exactly one named loopreview pane per herdr tab and swaps
its contents rather than proliferating panes. Selecting a target:
- If the pane already shows that exact target and
lris still running, it just focuses the tab. - Otherwise it reuses the pane in place: if
lris running it sendsqand waits (up to ~3s) for the shell to return, thencds into the target and relauncheslr. An idle shell is reused directly (noqis sent). A pane running some other program is never disturbed. - If in-place reuse is not safe — the pane is gone, process inspection fails, the quit times out, an unknown program is running, or the target path can't be safely quoted — it closes that pane (if any) and splits a fresh one.
A fresh pane opens according to pane_placement: the default rightmost
splits the tab's rightmost pane so it lands at the far-right edge of the tab
(falling back to the current pane when the tab's geometry can't be read), while
current splits the pane the popup was opened from.
A branch-diff open resolves its diff base in this order: origin/<base> (the
worktree's origin/<base> is fetched first, best-effort — an offline failure
just notes the comparison may be stale), else the local <base> ref (only
when the worktree isn't already on <base>, since comparing a branch against
itself is an empty diff), else it falls back to the working-tree view (plain
lr, with a note that no usable base ref was found). Worktree opens run lr in
the worktree's directory; PR opens run lr pr <ref> in the repo root.
Cleanup never happens automatically. Ctrl-D opens a confirmation modal that
shows each worktree's assessment (dirty / unpushed / clean) and any reason it is
considered in use. A worktree is in use — and cannot be removed even with
force — when any of these hold:
- It is open in a herdr workspace (
herdr worktree list). - A herdr pane's
cwd/foreground_cwdis inside it (herdr pane list). - A running process has its working directory inside it (
lsof, best-effort). - A live loopreview session is currently showing it.
Dirty or unpushed worktrees are refused by default; press f in the modal to
enable force for those (in-use is still refused). On removal the plugin runs
git worktree remove, then deletes the local branch only when it is provably
safe (has an upstream and is not ahead, or is merged into the default branch) —
the remote is never touched. The main worktree is never offered for cleanup.
Every lr this plugin launches hosts loopreview's control plane: a local
per-session socket that agents and scripts can drive with lr session *
(inspect the diff and threads, move the reviewer's view, leave draft comments,
wait for replies) — the TUI stays the human's. This pairs naturally with
reviewing agent worktrees: the agent that produced the change can steer the very
review you opened with the picker.
An agent running inside herdr (HERDR_ENV=1) can go one step further and
open the review itself, in its own tab, instead of waiting for a human to run the
picker: the headless herdr-loopreview open action points the tab's loopreview
pane at the agent's worktree, branch, or PR (see Command line).
Install the herdr-loopreview skill to teach an agent that:
npx skills add loopkeep/skills -s herdr-loopreviewTo steer the review once it is open — inspect the diff and threads, move the
reviewer's view, leave draft comments, wait for replies — install the
loopreview-session skill from
loopkeep/skills, the home of the loopkeep
family's agent skills:
npx skills add loopkeep/skills -s loopreview-sessionThe two compose: herdr-loopreview opens the pane, loopreview-session drives
the review.
| Action | What it does |
|---|---|
loopkeep.loopreview.picker |
The interactive TUI (default) |
loopkeep.loopreview.clean |
Open the picker straight into the cleanup review |
MIT