AI + debris. A small CLI for cleaning up the filesystem leftovers from AI
coding agents: Git worktrees, agent session stores, logs, and recorded-cwd
agent state — with generic build debris (node_modules, build caches) as
complementary coverage so scan stays a complete picture of one home.
AI tools are productive, but they shed a lot of temporary state while they branch, build, test, and retry. aibris scans the places that debris tends to collect, shows a readable cleanup plan, and only deletes after filters, confirmation, and path safety checks. Its subject is agent-produced state: worktrees, session transcripts, and recorded-cwd project stores. It complements general-purpose cleaners; it does not compete with them.
- Developers who use AI coding tools that create Git worktrees under
$HOME - Teams sharing development machines where worktrees accumulate
- Anyone who wants to reclaim disk space from node_modules and build caches
- AI assistants that need structured scan output before cleanup
| Category | Examples | Default clean |
|---|---|---|
| AI worktrees | Finite known containers plus $HOME conventions such as .tool/worktrees and project-local worktrees |
Classic: orphaned; guided Codex: evidence-based |
| Agent state | Claude and Cursor project stores | Orphaned only; no age gate |
| AI logs | Codex, Claude, Windsurf logs | Only with --risky |
| Dependencies | project node_modules directories |
Yes |
| Build caches | Go, npm, Gradle, Cargo, Xcode | Yes |
| Python caches | pip and uv cache directories | Yes |
The first three rows are agent-produced state — aibris's subject. The last
three are generic build debris: aibris covers them so scan reports a
complete picture of a home, but general-purpose cleaners already handle them
and winning on them is not an objective.
Agent-state scan rows expose a classification of live, orphaned, or
undetermined. This classification takes precedence over the classic age
filter: an absent recorded working directory proves the associated work is gone
and resume is already impossible, so an orphaned entry needs no age gate.
live and undetermined entries remain protected.
Issue #142 also uses installed, regenerable, and protected as a
planning taxonomy for six currently uncovered stores. Those terms are not
shipped categories, agent-state classifications, JSON fields, or CLI selectors.
Codex packages and Computer Use stay outside providers as installed content;
Codex tmp is only a future safety-bounded default-clean child-unit candidate;
generated images, Codex SQLite, and Cursor AI tracking remain protected and
cannot be unlocked by --risky alone. See
docs/CATEGORY.md for the store-specific future constraints.
aibris scan also emits a read-only protected-content inventory: protected
Codex session files under ~/.codex/sessions are aggregated by UTC month into
a top-level retention JSON object (also shown in human output) with
per-bucket unit/member counts, apparent bytes, and orphan statistics derived
from proven-absent recorded working directories. The inventory is
non-authorizing — it never creates cleanup candidates, never selects or mutates
members, and its partial state never blocks ordinary clean. The execution
layer (selector, manifest, executor) is deliberately parked; see
docs/PROTECTED_RETENTION.md.
curl -fsSL https://raw.githubusercontent.com/sungjunlee/aibris/refs/heads/main/install.sh | bashInstall from the current main branch when you want unreleased changes:
curl -fsSL https://raw.githubusercontent.com/sungjunlee/aibris/refs/heads/main/install.sh | bash -s -- mainInstall a specific release:
curl -fsSL https://raw.githubusercontent.com/sungjunlee/aibris/refs/heads/main/install.sh | bash -s -- 0.8.1The installer downloads GitHub Release binaries and verifies checksums.txt.
The default install path uses GitHub's releases/latest/download URLs for
prebuilt binaries. main builds from source with Go.
By default, aibris installs to ~/.local/bin and does not require sudo. If
that directory is not on your PATH, the installer prints the exact command to
add it for your shell. For a system-wide install, pass an explicit prefix:
curl -fsSL https://raw.githubusercontent.com/sungjunlee/aibris/refs/heads/main/install.sh | bash -s -- --prefix /usr/local/binWindows archives are experimental. See the canonical
Windows support contract for native installation, tested
behavior, and unaudited boundaries. install.sh remains Unix/Bash-only.
aibris scan # discover what's taking space
aibris scan --json # machine-readable output (see docs/JSON_SCHEMA.md)
aibris scan --root ~/.codex # narrow scan to a home subdirectory
aibris clean --dry-run # preview without deleting
aibris clean --no-guide --dry-run --json # redacted machine-readable cleanup plan
aibris clean --no-guide --dry-run --json --include-paths # opt in to plan paths
aibris clean --no-guide --json --force # execute and emit a redacted receipt
aibris clean --no-guide --json --interactive # silent stdin confirmations, JSON only
aibris clean --no-guide --dry-run # force classic cleanup audit
aibris clean # delete with confirmation
aibris clean --root ~/.codex --dry-run
aibris clean --age 7d # classic filter, or guided minimum idle age
aibris clean --age 30d # older than 30 days
aibris clean --age 1mo # older than 30 days (month shorthand)
aibris clean --age 1y # older than 365 days
aibris clean --interactive # confirm each item
aibris clean --category node_modules # only node_modules
aibris clean --tool codex,claude # only specific tools
aibris clean --risky # include ai-logs
aibris clean --include-active-worktrees # include active worktrees
aibris clean --force # skip confirmation promptSee docs/DOGFOOD.md for real local scan transcripts used to validate release behavior.
$ aibris scan
scan
roots ~
scanned 7 sources 4 items 3.2 GB
summary
found 4 items
found size 3.2 GB
default clean (estimate) 3.1 GB
protected 96.0 MB active worktrees; use --include-active-worktrees after review
by category
node_modules 1 1.8 GB
build-cache 2 1.3 GB
worktree 1 96.0 MB
largest
1.8 GB node_modules dashboard - 24d
842.0 MB build-cache go-build global 9d
512.4 MB build-cache npm global 18d
96.0 MB worktree b7f4c2 aibris active today
next
aibris clean --dry-run
aibris scan --json
The default clean figure is an estimate. scan applies the same existence
filtering and target normalization that clean applies, so nested or
duplicate targets count once; but clean-time safety protections (git safety,
overlap safety, scan-evidence filtering, physical owner checks) can only be
resolved by clean itself
and can only shrink the final plan. Run aibris clean --dry-run for the
exact plan.
Preview before deleting anything:
$ aibris clean --category worktree --age 7d --dry-run
clean
roots ~
policy age>7d, risky=false, active-worktrees=protected
scan cached, 8s old
scan summary
scanned 7 sources 3 items 2.0 GB
eligible 1 item 96.0 MB
protected/skipped 2 items 1.9 GB
by category
category found eligible protected/skipped main reason
worktree 2 192.0 MB 1 96.0 MB 1 96.0 MB active worktree protected
node_modules 1 1.8 GB 0 0 B 1 1.8 GB outside category/tool filters
matched 1 candidate 96.0 MB
clean plan
mode dry-run
targets 1 item 96.0 MB
targets
size category name project age/status action reason
96.0 MB worktree b7f4c2 aibris orphaned 12d remove-path orphaned worktree; parent repo metadata missing
~/.codex/worktrees/b7f4c2
[DRY-RUN] No files were removed.
For automation, use the phase-1 dry-run plan:
aibris clean --no-guide --dry-run --json
aibris clean --no-guide --dry-run --json --include-pathsThe default JSON output is one path-redacted clean_plan document on stdout
with empty stderr. It uses deterministic document-local target-1 and
row-1 IDs, keeps bytes only on containment-normalized physical targets, and
keeps exact/nested/ancestor discoveries as zero-byte logical rows. Guided JSON mode
accepts the normal deterministic defaults without prompting. --include-paths
opts in to explicit target paths, logical paths/projects, and cleanup commands.
Phase-1 clean fails closed before emitting this document if any scan provider
failed, so an emitted plan always has evidence.complete: true. In mixed
auto-guided output, policy.minimum_age remains the classic 7d filter while
the optional guided minimum idle age is reported separately as 3d.
Non-dry-run JSON requires --force or --interactive; either takes the
classic route and executes the plan built in the current process and emits one
versioned clean_receipt document. The receipt embeds the accepted redacted
plan, uses the same document-local physical target IDs, and exits zero only
for a succeeded status. --include-paths opts in to the same path, project,
and cleanup command fields as the dry-run plan.
JSON execution never writes prompts or progress text to stdout. For a classic
--force or --interactive execution, use the same selectors for preview
and execution (for example,
clean --no-guide --dry-run --json followed by
clean --no-guide --json --force), changing only --dry-run. With
--force, all selected physical targets are attempted. --interactive --json
reads one silent confirmation line per selected target in embedded
plan.physical_targets order: y/yes executes, n/no records a
non-requested skipped target, and invalid or missing input cancels the
remaining requests.
If deletion-time safety changes the selected physical-target set, JSON fails
closed before consuming any confirmation input.
requested always equals removed + partial + failed + cancelled; protected,
reviewable, and skipped targets are not requests. freed_bytes is credited
only when the physical owner is verified absent, so logical rows never add
bytes. JSON execution never accepts an external plan or receipt for replay.
When active Codex worktrees are the useful cleanup decision and no classic
cleanup selector is supplied, aibris clean --dry-run opens guided Codex
worktree review by default. This includes protected-only pressure: at least one
validated active Codex cleanup unit and either 256 MB total or three units. The
guide defaults recommended rows to selected, keeps reviewable and locked rows
visible, lets you toggle selectable rows by number, and still hands the final
selection to the normal dry-run plan before anything can be deleted:
aibris clean --dry-runThe guided policy operates on physical cleanup units. A unit is sized and removed once, but every direct or one-level nested Git worktree member must pass safety inspection. Members are grouped for retention by canonical Git common-dir, not by the path-derived project label.
Policy evaluation is ordered:
- Lock the unit when it contains the current directory, dirty or untracked files, unreadable Git or Codex activity evidence, a detached HEAD unreachable from named refs, or activity within the last 6 hours.
- Keep the three most recently active units per canonical repository as reviewable and unselected. A user may explicitly select these soft holds.
- Keep units younger than the guided minimum idle age (3 days by default) or smaller than 256 MB reviewable and unselected.
- Recommend and select the remaining units.
An attached local branch is recoverable even without an upstream. A detached
HEAD is recoverable when a local or remote named ref contains it. Missing or
gone upstream is shown as explanatory metadata and never locks a row by itself.
Changing --age or using the prompt's age, +, -, [ or ] commands
changes only the minimum idle age; the 6-hour lock and recent-three ranking do
not change.
The guide reads only Codex session metadata such as timestamps and working
directories, never conversation bodies. A real deletion still requires the
dry-run preview first and then the normal confirmation prompt unless --force
is explicitly provided. --force skips only that prompt: it cannot select a
locked row and is never passed to git worktree remove. Use --no-guide to
keep the classic cleanup audit/executor route, or --guide to force guided
Codex review.
When default guided review activates, it owns active Codex worktree decisions and merges them with the classic candidates into one unified cleanup review: selected guided parents and classic targets share one selection state, and a classic target nested inside a selected guided cleanup unit is reported as covered evidence rather than a second physical target. An empty guided selection therefore cannot hide classic candidates.
Cleanup overlap accounting follows one containment component from plan through receipt:
- The outermost executable target is the physical owner. Its on-disk size is counted once in found, eligible, selected, protected, planned, and freed totals. Nested and exact-path discovery rows remain visible as evidence and contribute no additional bytes.
- A
liveorundeterminedagent-state row anywhere above, below, or exactly on a cleanup target protects the complete component.--force, category selectors, and tool selectors cannot bypass this subtree shield. - When a generic outer owner contains orphaned agent state, the owner inherits
every canonical child revalidation obligation. All obligations must still be
orphaned immediately before the first mutation; otherwise the whole component
survives, freed bytes are zero, and the receipt identifies the blocking path
while unattempted obligations remain
not-attempted.
For piped or other non-TTY input, EOF accepts the guided default selection.
--dry-run then continues to the classic audit deterministically. In deletion
mode without --force, missing or declined guided confirmation aborts the
whole cleanup flow before classic targets can run.
Confirm before deleting anything:
$ aibris clean --category node_modules --age 7d
clean
roots ~
policy age>7d, risky=false, active-worktrees=protected
scan cached, 11s old
scan summary
scanned 7 sources 4 items 3.2 GB
eligible 1 item 1.8 GB
protected/skipped 3 items 1.4 GB
by category
category found eligible protected/skipped main reason
node_modules 1 1.8 GB 1 1.8 GB 0 0 B eligible for cleanup
build-cache 2 1.3 GB 0 0 B 2 1.3 GB outside category/tool filters
worktree 1 96.0 MB 0 0 B 1 96.0 MB active worktree protected
matched 1 candidate 1.8 GB
clean plan
mode delete
targets 1 item 1.8 GB
targets
size category name project age/status action reason
1.8 GB node_modules dashboard - 24d remove-path dependency directory; can be reinstalled
~/path/to/dashboard/node_modules
Proceed? [y/N]: y
removing 1/1: dashboard (node_modules) ...
removed: dashboard (node_modules) — 1.8 GB
cleanup receipt
targets 1 item
freed 1.8 GB
protected/skipped 3 items 1.4 GB
scan writes a short-lived snapshot under the user cache directory. A following
clean reuses it only when it is at most 5 minutes old and its normalized scan
roots, explicit cache revision (schema_version), and concrete provider
membership identity all match. A missing legacy identity or any mismatch falls
back to a live scan with progress output. The membership identity detects
provider additions, removals, and duplicate registrations, not behavior changes
inside an unchanged provider; those changes require a cache revision bump. Each
cached target also carries filesystem identity and type evidence. Missing,
replaced, type-changed, symlink, or Windows reparse-point targets reject cache
reuse instead of being trusted as the object that was scanned.
Live fallback keeps the same audit shape after non-interactive scan progress:
clean
roots ~
scanning node_modules
scanning build-cache
found build-cache 2 items 1.3 GB
found node_modules 1 items 1.8 GB
policy age>7d, risky=false, active-worktrees=protected
scan live
scan summary
scanned 7 sources 3 items 3.1 GB
eligible 1 item 1.8 GB
protected/skipped 2 items 1.3 GB
For unscoped guided Codex cleanup, the no-selector loop is fast and visible:
aibris scan
aibris clean --dry-run
aibris cleanThis plain-command pair is not a substitute for a scoped cleanup. If the user
approves selectors or safety flags, keep every flag and value identical in the
preview and execution commands and remove only --dry-run for execution.
When stdout is an interactive terminal, scans use a single-line spinner while
providers run. In non-interactive logs, progress falls back to plain
scanning / found lines.
If a provider fails but other providers return usable results, scan labels
the inventory as partial, lists the failed providers, emits the retained human
or JSON result, and exits with status 1. Partial scans are never cached for
cleanup, and clean requires a complete scan before it can plan or remove
anything. A partial scan also invalidates any previous cleanup scan cache.
Cancellation remains a hard failure.
- Independent age policies: classic cleanup defaults to
--age 7d, except for proof-classified orphaned agent state; guided Codex cleanup defaults to a 3-day minimum idle age while always keeping its 6-hour recent-activity lock and recent-three retention - Human age units support
h,d,w,mo, andy - Low classic age warnings describe the widened minimum-age eligibility within the selected category/tool scope; they do not imply that risky, active-worktree, agent-state, overlap, or Git protections are bypassed
--dry-runpreviews before deleting--interactiveconfirms each item- Target plan before final confirmation shows category, size, project, age/status, path, cleanup command when applicable, and zero-byte nested overlap lineage
- Guided Codex cleanup classifies physical units as recommended,
reviewable, or locked after member-level Git and activity checks, then uses
the same dry-run and confirmation model as regular
clean - Git-aware active removal preflights every member, removes it with
git worktree removesemantics, preserves attached branch refs and referenced detached commits, and verifies parent worktree metadata. It never falls back to recursive deletion after Git removal fails. - Recent scan reuse skips a repeated scan when
cleancan use a fresh snapshot with matching roots, cache revision, and concrete provider membership. It binds each target to filesystem identity and type evidence, refreshes current modification time before selection, and verifies identity, type, age, and modification time again at the mutation boundary --riskymust be explicitly set to delete AI logs- Active worktrees are excluded by default; use
--include-active-worktreesonly when you intentionally want age-based cleanup for valid worktrees - Recorded-cwd volume boundaries fail closed: agent-state classification
compares the nearest existing ancestor with its parent. Unix uses device
identity and Windows uses
GetVolumePathNameW; a different volume or a lookup failure leaves the entryundeterminedand protected. - Home-scoped roots: default scanning starts at
$HOME;--rootcan narrow scope to one or more existing directories under$HOME - Convention-based worktree discovery: worktrees are discovered by finding
worktrees,worktree,worktree-*, andworktrees-*directories under scan roots, then validating direct or one-level nested.gitfiles. The generic fallback keeps a container depth limit of 4. A separate finite exact registry covers~/.codex/worktrees,~/.relay/worktrees,~/.gstack/worktrees, and~/.config/superpowers/worktreeswithout opening unrelated hidden-owner fanout. Superpowers rows usesource=superpowersandtool=unknown. - Pruned scan directories for project-style discovery include
.Trash,Library,Applications,Pictures,Movies,Music,.git,vendor, and nestednode_modules;DesktopandDownloadsare scanned - Official cache cleanup commands are preferred for supported caches
(
go clean -cache,npm cache clean --force,uv cache prune). If the owning command is missing, aibris falls back to the existing safe path removal behavior; if the command runs and fails, aibris does not fall back silently. - Confirmation prompt on every
clean(use--forceto skip only the prompt; hard locks and non-forced Git removal remain unchanged) - Safety validation rejects deletions outside
$HOME, symlink escapes, and unvalidated arbitrary paths. Generic worktrees are only cleanable after scan metadata proves they are active or orphaned Git worktrees. A readable unit with missing or invalid direct/one-level metadata remains visible as oneplain-dirowner row for review; mixed valid/invalid members protect the whole owner.plain-dir, empty, and unknown worktree statuses are never cleanup candidates. - Negative age rejection prevents accidental full-scope deletion
aibris scan → discovers worktree conventions, caches, node_modules, logs under scan roots
aibris clean → filters or plans evidence-based units → previews → deletes safely
AI tools leave debris in predictable conventions. aibris scans $HOME by
default, prunes high-noise system and media directories while discovering
development debris, validates Git worktree metadata before reporting worktrees,
measures disk usage, and cleans only after filters and safety checks.
Judgment about what should be removed stays with a human or an AI assistant
using scan --json.
New tools can be added by implementing the DebrisProvider interface.
No-selector guided Codex cleanup:
aibris scan --json
aibris clean --dry-run
aibris cleanScoped cleanup keeps every approved selector and safety flag identical between
preview and execution; only --dry-run is removed:
aibris scan --json
aibris clean --no-guide --category worktree --age 7d --dry-run
aibris clean --no-guide --category worktree --age 7dThe intended agent flow is: scan, summarize by project/category/age, use guided
review for active Codex pressure, run a dry-run, ask again, then execute. Treat
active as linked Git metadata, not proof of recent use; rely on the guided
class and reason before proposing an active unit. A scoped execution must never
fall back to plain aibris clean: preserve all approved --category, --tool,
--root, --age, routing, and safety flags.
See CONTRIBUTING.md and AGENTS.md for architecture and development guidelines.
See ROADMAP.md. The project intentionally remains in the 0.x series until the maintainer is satisfied; milestones are capability gates, not promised release dates or an implied v1.0.0 schedule.
MIT — see LICENSE.