{"id":51015725,"url":"https://github.com/ai-ecoverse/gh-reaper","last_synced_at":"2026-06-21T10:02:16.682Z","repository":{"id":362556541,"uuid":"1259684448","full_name":"ai-ecoverse/gh-reaper","owner":"ai-ecoverse","description":"Harvest stale git worktrees: find them by age \u0026 size across your machine and reap them. Read-only by default, macOS TCC-safe. (Don't Fear) The Reaper for forgotten worktrees.","archived":false,"fork":false,"pushed_at":"2026-06-04T19:54:34.000Z","size":426,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-04T21:38:17.072Z","etag":null,"topics":["cleanup","cli","developer-tools","gh-extension","git-worktree","macos"],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ai-ecoverse.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-04T18:52:03.000Z","updated_at":"2026-06-04T19:54:39.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ai-ecoverse/gh-reaper","commit_stats":null,"previous_names":["ai-ecoverse/gh-reaper"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/ai-ecoverse/gh-reaper","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ai-ecoverse%2Fgh-reaper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ai-ecoverse%2Fgh-reaper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ai-ecoverse%2Fgh-reaper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ai-ecoverse%2Fgh-reaper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ai-ecoverse","download_url":"https://codeload.github.com/ai-ecoverse/gh-reaper/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ai-ecoverse%2Fgh-reaper/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34605335,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-21T02:00:05.568Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cleanup","cli","developer-tools","gh-extension","git-worktree","macos"],"created_at":"2026-06-21T10:02:15.801Z","updated_at":"2026-06-21T10:02:16.664Z","avatar_url":"https://github.com/ai-ecoverse.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gh-reaper\n\n[![99% Vibe_Coded](https://img.shields.io/badge/99%25-Vibe_Coded-ff69b4?style=for-the-badge\u0026logo=claude\u0026logoColor=white)](https://github.com/ai-ecoverse/vibe-coded-badge-action)\n\n![gh-reaper — don't fear the worktrees](hero-banner.jpg)\n\n\u003e _\"Seasons don't fear the reaper / Nor do the wind, the sun, or the rain...\"_\n\u003e — and neither do the dozen git worktrees you spun up six months ago and forgot about.\n\n**`gh reaper`** is a [GitHub CLI](https://cli.github.com/) extension that hunts down stale **git worktrees** across your machine and tells you how long each has been gathering dust and how much disk it's hoarding. It's **read-only by default** — it just shows you the graveyard. Add `--reap` when you're ready to actually delete.\n\n```\nAGE      SIZE  STATUS    BRANCH                 PATH\n8mo    160.7M  clean     opencode-blog-archive  ~/Developer/helix-website/.conductor/opencode-blog-archive\n7mo    116.1M  dirty     submit-to-zed-registry ~/Developer/zed-elvish/.conductor/islamabad\n7mo      6.0M  orphan    ?                      ~/Developer/tree-sitter-fountain-forced-scene-headings\n6mo      544K  clean     renovate/actions-check ~/Developer/aem-boilerplate/.yolo/claude-1\n2h       2.8G  merged    migrate-vfs-to-opfs    ~/intent/workspaces/restricted-implement/slicc\n```\n\n(That last one was touched two hours ago, so age alone would never flag it — but its branch is already merged, so it's safe to reap. That's the `merged` signal earning its keep.)\n\nWorktrees are wonderful — until you have forty of them. Tools like [Conductor](https://conductor.build), `git worktree add`, and various AI agents scatter checkouts all over your disk, and `git worktree prune` only cleans up the ones whose directories are *already gone*. `gh reaper` finds the living-but-forgotten ones.\n\n## Features\n\n- **Machine-wide discovery** — sweeps your usual code roots (`~/Developer`, `~/conductor`, `~/intent/workspaces`, `~/.codex/worktrees`, `~/Projects`, `~/go/src`, …) for linked worktrees, not just the repo you're standing in. Knows where agent tools (Conductor, yolo, intent, Codex) stash their checkouts — and the worktrees agents nest *inside* a repo are found automatically, since those repos already sit under scanned roots: Claude Code's `\u003crepo\u003e/.claude/worktrees/`, Gemini CLI's `\u003crepo\u003e/.gemini/worktrees/`, and Qwen Code's `\u003crepo\u003e/.qwen/worktrees/`.\n- **Age \u0026 size at a glance** — every worktree shows when it was last touched and how much disk it occupies, sorted oldest-first. \"Touched\" means the newest **non-gitignored** change, so a routine `npm install` or build doesn't make a months-old worktree look brand new.\n- **Knows what's already done** — flags worktrees whose `HEAD` is already merged into the default branch (`merged`), and optionally asks `gh` whether a branch's PR was squash-merged (`pr-merged`, via `--check-prs`). Sweep just the finished ones with `gh reaper --merged --reap`.\n- **Safety classification** — each worktree is tagged `merged`, `clean`, `dirty`, `unpushed`, or `orphan`. Risky ones are never reaped without `--force`. Regenerable dependency lock files (`package-lock.json`, `yarn.lock`, `Cargo.lock`, …) are treated like `.gitignore`d files, so a stray `npm install` doesn't flag a finished worktree as `dirty` (opt out with `--no-ignore-locks`). An agent's own session marker (like Qwen Code's `.qwen-session`) is ignored the same way — and always — so a worktree an agent created and abandoned still reads as `merged`/`clean` and sweeps with `gh reaper --merged --reap`.\n- **macOS-friendly** — scans a curated set of dev directories by default, so it's fast and **never trips macOS privacy (TCC) permission prompts** for Desktop, Documents, Downloads, Photos, and friends.\n- **Reaps the right way** — uses `git worktree remove` (run from the main worktree) so git's bookkeeping stays consistent; `--prune` tidies the admin entries afterward.\n- **Read-only by default** — bare `gh reaper` only lists; nothing is deleted until you pass `--reap`. Then confirm each one, `[a]ll` at once, or add `--yes` to sweep unattended. `--json` for the scripty.\n\n## Installation\n\n```bash\ngh extension install ai-ecoverse/gh-reaper\n```\n\nUpgrade later with:\n\n```bash\ngh extension upgrade reaper\n```\n\n**Requirements:** `git`, plus the usual POSIX suspects (`find`, `du`, `stat`, `awk`). `jq` is only needed for `--json`. Works on macOS (BSD tools) and Linux (GNU tools).\n\n## Usage\n\n```bash\n# Survey forgotten worktrees -- read-only, the default\ngh reaper\n\n# Only the truly stale: untouched 30+ days and bigger than 100 MB\ngh reaper --min-age 30 --min-size 100\n\n# Sweep only worktrees whose work is already merged (incl. squashed PRs)\ngh reaper --merged --check-prs --reap\n\n# Scan a specific spot, then reap interactively\ngh reaper ~/work/monorepo --reap\n\n# Reap everything 2+ weeks old, no prompts\ngh reaper --min-age 14 --reap --yes\n\n# Machine-wide survey (still skips TCC-protected dirs)\ngh reaper --all\n\n# Pipe the inventory into your own tooling\ngh reaper --json | jq '.[] | select(.sizeKb \u003e 100000) | .path'\n```\n\n### Options\n\n```\ngh reaper [OPTIONS] [PATH...]\n\n  -r, --reap           Delete worktrees (interactive unless --yes). Without this,\n                       gh reaper only lists -- it never removes anything.\n  -d, --min-age DAYS   Only show worktrees untouched for at least DAYS (default: 0)\n  -s, --min-size MB    Only show worktrees at least MB megabytes in size (default: 0)\n  -m, --merged         Only show worktrees whose HEAD is merged into the default\n                       branch (with --reap, sweep exactly the done ones)\n      --check-prs      For non-merged branches, ask 'gh' whether their PR was\n                       merged (catches squash-merges; needs gh auth + network)\n      --no-ignore-locks  Count dependency lock-file changes as dirty (by default\n                       a worktree whose only change is lock churn is not dirty)\n  -p, --path DIR       Add a scan root (repeatable; also accepted as PATH args)\n  -j, --jobs N         Parallel inspection workers (default: CPU count; 1 = serial)\n  -a, --all            Scan all of $HOME (still skips TCC-protected dirs)\n  -y, --yes            With --reap, delete without prompting for each worktree\n  -f, --force          With --reap, also remove dirty, unpushed, or orphaned worktrees\n      --prune          With --reap, run 'git worktree prune' on touched repos afterward\n      --json           Emit results as JSON (always read-only)\n      --dry-run        List only, change nothing (this is the default; kept for habit)\n      --no-color       Disable colored output\n  -v, --version        Show version\n  -h, --help           Show this help\n```\n\n### Status flags\n\n| Status      | Meaning                                                        | Reaped on `--reap`? |\n| ----------- | ------------------------------------------------------------- | ------------------- |\n| `merged`    | HEAD is already in the default branch — the work is done       | ✅ yes               |\n| `pr-merged` | The branch's PR was merged (squash/rebase); `--check-prs` only | ✅ yes               |\n| `clean`     | No local changes; HEAD exists on a remote — safe to delete     | ✅ yes               |\n| `dirty`     | Uncommitted or untracked changes present                       | 🔒 needs `--force`  |\n| `unpushed`  | Commits that exist nowhere on a remote (would be lost)         | 🔒 needs `--force`  |\n| `orphan`    | The main repository is gone; only the lonely checkout remains  | 🔒 needs `--force`  |\n\nA worktree can combine flags — e.g. `dirty merged` means the commits are merged but\nthere are still uncommitted edits, so it needs `--force`. `merged`/`pr-merged`\nsupersede the `unpushed` warning, since those commits are accounted for upstream.\n\n**Lock files don't count as dirty.** Regenerable dependency lock files\n(`package-lock.json`, `npm-shrinkwrap.json`, `yarn.lock`, `pnpm-lock.yaml`,\n`bun.lockb`/`bun.lock`, `composer.lock`, `Gemfile.lock`, `Cargo.lock`,\n`poetry.lock`, `Pipfile.lock`, `go.sum`, `flake.lock`, `gradle.lockfile`,\n`packages.lock.json`) are treated like `.gitignore`d files: a worktree whose\n*only* change is lock-file churn isn't `dirty`, and lock mtimes don't count toward\nits age — so a `npm install` that rewrites `package-lock.json` can't keep a\nfinished worktree out of the harvest. A lock change next to any real edit still\ncounts as dirty. Pass `--no-ignore-locks` to disable this.\n\n**Agent session markers don't count as dirty either.** Coding agents that spin up\ntheir own git worktree drop a little bookkeeping file into it — Qwen Code, for\ninstance, writes a session-id pointer to `\u003crepo\u003e/.qwen/worktrees/\u003cslug\u003e/.qwen-session`.\nLeft uncounted, that marker would brand *every* worktree the agent ever made as\n`dirty` and keep `gh reaper --merged --reap` from sweeping it. So these markers are\ntreated like `.gitignore`d files — unconditionally (there's no `--no-ignore-locks`\nescape hatch; a session pointer is never authored work). Gemini CLI and Claude\nCode nest their worktrees the same way but leave the tree clean, so they need no\nspecial handling.\n\n## How it works\n\n**Discovery.** A linked worktree is marked by a `.git` *file* (not a directory) whose contents point into `…/worktrees/…`. `gh reaper` finds those with a single pruned `find` per root, skipping `node_modules`, caches, `Library`, `*.noindex`, and the like — and skipping submodules (whose `.git` files point into `…/modules/…`).\n\n**Scan roots.** By default it only walks well-known developer directories that actually exist on your machine, plus your current directory. This keeps it fast and, on macOS, keeps it clear of the TCC-protected folders (Desktop, Documents, Downloads, Pictures, Movies, Music, `Library`, iCloud Drive, external `/Volumes`) that would otherwise pop a permission dialog. `--all` opts into a full `$HOME` sweep but **still** hard-skips those protected locations. Point it anywhere explicitly with a `PATH` argument or `--path`.\n\n**Speed.** The slow part of a scan is sizing (`du`) and classifying (`git`) each worktree, so those run in parallel across workers (default: your CPU count). On a real 238-worktree / 114 GB machine this cut a scan from ~121s to ~31s. Tune with `--jobs N`, or `--jobs 1` to force serial. Results are identical either way.\n\n**\"Last touched.\"** Age is the newest **non-gitignored** change: the most recent mtime among tracked and untracked-but-not-ignored files (`git ls-files`), combined with the last commit date. Build output, `node_modules`, caches and other gitignored churn are excluded — so a routine `npm install`, build, or automated `git checkout` no longer resets a worktree's apparent age. (Earlier versions used the working-directory and reflog mtimes, which a single install or sync could bump, making months-stale worktrees look brand new.) Orphans, where git can't enumerate the tree, fall back to the working-dir / reflog mtime.\n\n**Merged detection.** Beyond age, each worktree is checked for whether its work is already done. The offline check is `git merge-base --is-ancestor HEAD \u003cdefault-branch\u003e` — if true, every commit is already in the default branch, so the worktree is tagged `merged` regardless of how recently its files were touched. GitHub's \"Squash \u0026 merge\" rewrites history, so a merged branch may not be an ancestor; `--check-prs` covers that by asking `gh pr list --state merged` per unconfirmed branch (networked, opt-in) and tagging matches `pr-merged`. Either way, merged work is safe to reap without `--force` (unless the working tree is also dirty).\n\n**Reaping** happens only under `--reap` — bare `gh reaper` is a read-only listing. Clean worktrees are removed with `git worktree remove` executed from the *main* worktree (so git won't refuse to remove \"the current working tree\"). Dirty/unpushed worktrees get `--force` only when you pass `--force`. True orphans — whose main repo is gone, so git can't help — are removed with `rm -rf`, and also only under `--force`.\n\n## Safety\n\n- **Read-only by default.** Nothing is ever removed unless you pass `--reap`; `--dry-run` and `--json` are always read-only, and `--yes`/`--force` do nothing on their own.\n- `dirty`, `unpushed`, and `orphan` worktrees are **skipped unless `--force`** — your uncommitted edits and unpushed commits are safe by default.\n- With `--reap`, interactive mode asks per worktree (`[y]es / [n]o / [a]ll / [q]uit`); add `--yes` to skip the prompts.\n- It only ever targets linked worktrees — it will never offer to delete a main repository.\n\n\u003e _More cowbell strongly recommended but not required._\n\n## Uninstall\n\n```bash\ngh extension remove reaper\n```\n\n## License\n\n[Apache-2.0](LICENSE) © the AI Ecoverse contributors.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fai-ecoverse%2Fgh-reaper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fai-ecoverse%2Fgh-reaper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fai-ecoverse%2Fgh-reaper/lists"}