Find and clean stale
git worktreeentries across all your repos — interactive TUI for humans, JSON output for AI agents and scripts.
wtkill is the npkill of git worktrees. Point it at a directory full of repos, it shows you every worktree across all of them with size and age, and you delete the stale ones with one keystroke.
It also exposes a clean non-interactive CLI with JSON output so AI agents (Claude Code, Codex, Cursor, etc.) and shell scripts can use it without a terminal.
Worktrees are great until you forget them. Six months in you have 60 GB of branches you'll never touch again, scattered across .worktrees/, .claude/worktrees/, and one-offs you made on a Tuesday. git worktree list only shows them per-repo, and du on ~/Projects lies to you because it counts symlinks weirdly.
wtkill walks your projects directory, runs git worktree list --porcelain on every repo it finds, enriches each entry with du -sk and git log -1, and renders the whole picture sorted by what's eating the most disk.
- Recursive scan of any directory looking for git repos (default
~/Projects, configurable) - Live concurrent scan — 8 worktrees enriched in parallel via goroutines, results stream into the UI as they arrive
- Symlink-aware dedupe so repos that share a
.gitvia symlink only show their worktrees once - TUI with selection, sorted-by-size, status indicators (
prunable,missing,deleted,failed), age-coloured (green ≤7d, yellow ≤30d, red >30d) - Non-interactive CLI with subcommands
list,rm,cleanand JSON output by default when stdout is piped - Filters for
listandclean:--older-than,--branch <regex>,--prunable,--missing,--min-size - Safety:
cleanwithout filters refuses to run;--dry-runpreviews;--forceis opt-in - Single static binary, ~4.6 MB, no runtime dependencies
Requires Go 1.25+.
git clone https://github.com/ohernandezdev/wtkill
cd wtkill
go build -o wtkill .
mv wtkill ~/.local/bin/ # or anywhere on $PATHbrew install ohernandezdev/tap/wtkillTarballs for darwin-arm64, linux-amd64, linux-arm64 are on the releases page.
# example: macOS Apple Silicon
curl -L https://github.com/ohernandezdev/wtkill/releases/latest/download/wtkill-v0.3.0-darwin-arm64.tar.gz | tar xz
mv wtkill ~/.local/bin/ # or anywhere on PATHwtkill # scan ~/Projects
wtkill --path ~/code # scan a different root
wtkill --depth 6 # walk deeper than the default 4
wtkill --include-main # also list each repo's main worktreeKeys
| key | action |
|---|---|
↑↓ / j k |
navigate |
PgUp / PgDn |
jump a page |
g / G |
first / last |
space |
delete selected worktree |
f |
toggle --force (for worktrees with uncommitted changes) |
m |
toggle including each repo's main worktree (rescans) |
r |
rescan |
q / Ctrl+C |
quit |
When a delete fails, the row turns ✗ failed and the footer shows git's error message for that row. Press f to enable --force, then space again to retry.
When stdout is not a TTY, wtkill list and wtkill rm automatically emit JSON.
# JSON list of every worktree
wtkill list --json
# Filtered text list
wtkill list --older-than 30d --min-size 100MB
# Bulk-clean: preview what 60-day-old branches would be removed
wtkill clean --older-than 60d --dry-run
# Bulk-clean: remove every worktree git considers prunable
wtkill clean --prunable --force
# Remove specific paths
wtkill rm /path/to/worktree-a /path/to/worktree-b --force{
"results": [
{
"path": "/Users/me/Projects/foo/.worktrees/bar",
"repo": "/Users/me/Projects/foo",
"ok": true
}
],
"failed": 0
}clean adds dry_run, matched, removed, failed, freed_bytes at the top level.
Applies to list and clean.
| flag | example | meaning |
|---|---|---|
--older-than <dur> |
--older-than 30d |
last commit older than this (s m h d w) |
--branch <regex> |
--branch '^claude/' |
branch name matches regex |
--prunable |
only worktrees git already considers prunable | |
--missing |
only worktrees whose path no longer exists on disk | |
--min-size <size> |
--min-size 500MB |
minimum on-disk size (B KB MB GB TB) |
| code | meaning |
|---|---|
| 0 | success |
| 1 | one or more operations failed (partial success) |
| 2 | usage error (bad flag, missing required filter, etc.) |
Drop this into your agent's system prompt or tool definition:
You have access to the
wtkillCLI. Usewtkill list --jsonto discover git worktrees under the user's projects directory, thenwtkill rm <path> --force --jsonto remove specific ones, orwtkill clean <filters> --dry-run --jsonto preview a bulk operation. Always do a--dry-runbefore anycleanand surface what it would remove to the user.
Because output is JSON by default off-TTY and exit codes are well-defined, agents can parse results without regex on stderr output.
- Discovery —
wtkillwalks the configured root withos.ReadDir, depth-limited, skipping common heavy dirs (node_modules,target,.next, etc.). A directory is a repo if it contains a.gitentry. - Dedupe — for each candidate repo it runs
git rev-parse --absolute-git-dir, resolves symlinks viafilepath.EvalSymlinks, and dedupes so two paths sharing the same.gitonly get scanned once. - Enrichment — for each repo it calls
git worktree list --porcelainto get the worktree list, then for each non-main worktree it runsdu -skfor size andgit log -1 --format=%ctfor age. These are dispatched as goroutines bounded by a semaphore (8 concurrent). - Render — the TUI is Bubble Tea (Elm-Architecture model/update/view) styled with Lip Gloss. Updates stream in from a channel as worktrees are enriched; the list re-sorts by size on every insert.
- Delete —
git worktree remove <path>(with--forceif toggled), followed bygit worktree pruneto clean stale admin entries.
- It does not delete branches, tags, or remotes. Only worktree entries.
- It does not touch your main worktree unless you pass
--include-main. - It is not a replacement for
npkill— npkill is fornode_modules; this is for git worktrees. Different scope. - It does not currently know how to GC orphan
.git/worktrees/<name>/admin directories that git left behind after a manualrm -rf. Usegit worktree prunefor that.
PRs welcome. The codebase is small and intentionally so:
main.go— entrypoint, subcommand dispatch, version, helpscan.go— concurrent repo discovery, worktree enrichment, symlink dedupe,git worktree removemodel.go— Bubble Tea model + Lip Gloss rendercmd.go— non-interactivelist/rm/cleanand filter parsing
Before opening a PR:
go fmt ./...
go vet ./...
go build -o wtkill .
./wtkill list --json --depth 2 | head -20 # smoke testMIT © Omar Hernández (@ohernandezdev)
Built on top of Bubble Tea and Lip Gloss by @charmbracelet. Inspired by npkill.

{ "root": "/Users/me/Projects", "scanned_repos": 122, "elapsed_ms": 15386, "total_bytes": 19789389824, "count": 8, "worktrees": [ { "repo": "/Users/me/Projects/my-app", "path": "/Users/me/Projects/my-app/.worktrees/feature-x", "branch": "feature-x", "head": "e5d70bfe944da1ab956ad1177876767fc99ebede", "detached": false, "bare": false, "prunable": false, "missing": false, "size_bytes": 4519583744, "age_days": 9 } ] }