Install
macOS (universal, Apple Silicon and Intel):
brew install tdermendjiev/roomy/roomy
Linux (x86_64 and arm64, one static binary, no runtime to install):
sudo dpkg -i roomy_1.3.0_amd64.deb # Debian, Ubuntu: installs /usr/bin/roomy, systemd units, /etc/roomy/policy.json
sudo rpm -i roomy-1.3.0-1.x86_64.rpm # RHEL, Fedora
apk add --allow-untrusted roomy_1.3.0_x86_64.apk # Alpine
tar xzf roomy-1.3.0-linux-amd64.tar.gz && sudo install -m 755 roomy /usr/bin/
docker run --rm ghcr.io/tdermendjiev/roomy version # container image
Then check the environment:
roomy doctor
doctor reports whether du, git, docker and conda are available, whether the Trash and quarantine directory are usable, the volume capacity and the license in effect. Nothing is modified except creating the quarantine directory.
/etc/roomy/policy.json. On Linux the packages also install the fleet systemd units and the hub service; both are inactive until enabled.Commands
roomy scan [paths…] [--json] [--plan FILE] … find reclaimable artifacts (never deletes)
roomy clean --plan FILE [--dry-run] [--yes] … remove exactly what the plan lists
roomy purge [--older-than DAYS] [--trash] empty expired quarantine (and Linux Trash)
roomy restore [PATH] list or restore quarantined items
roomy rules [--json] [--platform P] show the rule catalog
roomy doctor environment check
roomy fleet … policy-driven, unattended runs → Fleet
roomy hub … central inventory and dashboard → Hub
roomy version [--json]
The typical flow is three commands. The first produces data, the second writes a file, the third does exactly what the file says.
roomy scan --json # what could be reclaimed, as data
roomy scan --plan plan.json # write a plan (safe tier by default)
roomy clean --plan plan.json # remove exactly what the plan lists
Exit codes
| Code | Meaning |
|---|---|
0 | OK, including "nothing to do" |
1 | Error, or clean refused to proceed without confirmation |
3 | Finished, but some items failed to dispose |
4 | Another fleet run holds the lock |
5 | clean refused the plan because it contains items above the allowed safety tier |
64 | Usage error (bad flag or value) |
roomy scan
Finds artifacts under one or more roots (default: the home directory) and probes the fixed-path tool caches for the current platform. It never deletes.
roomy scan [<roots>…]
--home DIR home to probe for tool caches (default: current user)
--categories a b … system categories: aiTools agentHost xcode globalCaches globalModules python homebrew docker
--ecosystems a b … ai npm py rust ruby go java swift dotnet general
--exclude P … paths or globs to skip
--rules FILE … extra rule catalogs merged over the built-in one
--disable-rule ID … switch rules off
--no-system | --no-projects
--json full ScanReport on stdout
--plan FILE write a CleanupPlan
--plan-tier safe|caution|warning highest tier admitted to the plan (default safe)
--min-idle-days N only plan items idle ≥ N days
--require-known-idle exclude items whose idle time is unknown
--method trash|quarantine|delete disposal recorded in the plan
--top N table rows (default 40)
-q, --quiet no progress on stderr
The walk never enters tool-owned trees such as ~/.nvm, ~/.cargo, ~/.vscode-server or ~/Library, because a node_modules inside them is part of a tool's install, not a project artifact. The system probes cover the ones worth reclaiming. A skipped directory is still scanned when it is given explicitly as the root.
ScanReport JSON
--json prints a ScanReport with schemaVersion: 1. The shape is additive-only: fields are added, never renamed or removed.
{
"schemaVersion": 1, "roomyVersion": "1.3.0", "catalogVersion": "2026.10.27",
"generatedAt": "2026-10-27T00:00:00Z", "host": "agent-07", "platform": "linux",
"scanRoot": "/home/agent", "home": "/home/agent", "durationSeconds": 1.2, "partial": false,
"totalBytes": 1610612736,
"bytesByTier": { "safe": 1500000000, "caution": 0, "warning": 1048576 },
"bytesByCategory": { "project": 900000000, "aiTools": 600000000, "globalCaches": 110000000 },
"bytesByEcosystem": { "py": 900000000, "ai": 600000000, "npm": 110000000 },
"projectCount": 3,
"volume": { "totalBytes": 1374000000000, "availableBytes": 1288000000000 },
"items": [
{
"id": "9f1c…", // stable per path
"path": "/home/agent/.cache/uv", "displayPath": "~/.cache/uv",
"label": "uv cache", "bytes": 492000000,
"tier": "safe", "ecosystem": "py", "category": "globalCaches",
"group": "Global package caches", "source": "systemProbe", "ruleID": "uv-cache",
"restoreHint": "uv cache clean",
"lastActivity": "2026-10-20T10:00:00Z", "idleDays": 7, "activity": "recent"
}
]
}
category is project for project-walk findings or the system category key. source is one of projectWalk, systemProbe, dockerCLI, condaCLI, pipCLI; the last three have synthetic paths such as docker:prune:dangling, conda:env:foo or pip:orphan:/path/.venv.
roomy clean
Executes a plan file and nothing else.
roomy clean --plan FILE
--method M override the plan's method
--allow-caution admit review-tier items
--allow-irreversible admit irreversible items (session history, archives, volumes)
--dry-run report, touch nothing
-y, --yes skip confirmation (required when stdin is not a TTY)
--json summary on stdout
--audit FILE audit log (default ~/.roomy/audit.jsonl)
--quarantine-dir DIR (quarantine) default ~/.roomy/quarantine or $ROOMY_QUARANTINE_DIR
--retention-days N recorded for purge (default 7)
--actor NAME recorded in the audit log, e.g. "claude-code"
Rules of engagement
- Only paths in the plan are touched. Items that no longer exist are skipped and reported.
- Tier is enforced at clean time, not plan time: a plan containing a
warningitem is refused with exit 5 unless--allow-irreversibleis passed, even if the plan was written with--plan-tier warning. - Without a TTY,
--yesis mandatory. Agents should pass--yesonly after inspecting the plan. - Every receipt and failure is appended to the audit log, with the
--actorname.
Disposal methods
| Method | What happens | Undo |
|---|---|---|
trash | Finder Trash on macOS, the freedesktop Trash on desktop Linux. Default on macOS. | Put back from the Trash |
quarantine | Rename into ~/.roomy/quarantine on the same volume with a manifest. Instant. Default on Linux. | roomy restore until purged |
delete | Permanent removal. | None |
Docker, Conda and pip findings are always cleaned through their own tool (docker rmi, conda clean, pip uninstall), whatever the method.
CleanupPlan JSON
A plan is a small JSON file, schemaVersion: 1. roomy scan --plan writes one; any tool may write one by filtering a report's items and keeping the fields below.
{
"schemaVersion": 1, "createdAt": "…", "host": "…", "method": "quarantine", "reason": "roomy scan --plan",
"items": [
{ "id": "…", "path": "…", "label": "…", "bytes": 1, "tier": "safe",
"category": "project", "source": "projectWalk", "ruleID": "node_modules" }
]
}
roomy clean validates existence and tier of every item at run time, so a stale or over-eager plan fails closed.
roomy purge and roomy restore
Quarantine keeps a <timestamp>-<name>.roomy.json manifest next to each item with the original path, size and time.
roomy restore # list what is in quarantine
roomy restore legacy-app/node_modules # move one item back (path suffix)
roomy purge --older-than 7 # permanently remove expired items; this is when space is freed
roomy purge --trash # also empty the freedesktop Trash on Linux
Items on another volume are quarantined in <mount>/.roomy-quarantine so the move stays a rename.
roomy rules and roomy doctor
The rule catalog is data, embedded into every binary. roomy rules prints project rules and system rules for a platform; --json prints the whole catalog in the same schema accepted by --rules FILE and a policy's scan.extraRules.
roomy rules --platform linux
roomy rules --json > catalog.json
roomy scan --rules /etc/roomy/rules.d/our-agent.json # merge a custom catalog
roomy scan --disable-rule cursor-state-backup # switch one rule off
A custom catalog uses the same schema. Only id is required; a rule with an existing id replaces the built-in one, new ids are appended. That is how a fleet adds a new agent's cache directory without waiting for a release.
{
"schemaVersion": 1,
"systemRules": [
{
"id": "our-agent-cache",
"category": "agentHost", // aiTools agentHost xcode globalCaches globalModules python homebrew docker
"label": "Our agent's scratch cache",
"ecosystem": "ai",
"tier": "safe", // safe | caution | warning
"restoreHint": "Recreated on the next run",
"activity": "newestChild", // none | self | newestChild
"paths": { "linux": ["$XDG_CACHE_HOME/our-agent"], "macos": ["Library/Caches/our-agent"] }
}
]
}
Path specs are home-relative by default; ~/… or absolute paths, $VAR with XDG defaults, and globs in any component are allowed. Findings under 1 MB are dropped.
Safety tiers in the CLI
| Tier | Meaning | scan --plan | clean |
|---|---|---|---|
| safe | Fully regenerable | Default | Default |
| caution | Regenerable at a cost, or mixed with user data | --plan-tier caution | --allow-caution |
| warning | Not regenerable: session history, archives, volumes | --plan-tier warning | --allow-irreversible |
Linux notes
Trash vs quarantine
Desktop Linux has the freedesktop Trash and Roomy supports it (--method trash). On a headless host nothing ever empties the Trash, so space is never reclaimed while the user believes it was. Roomy's default on Linux is therefore quarantine: items are renamed into ~/.roomy/quarantine with a manifest, and roomy purge or a fleet run with purgeOnRun frees them after the retention period. The agentHost category also reports an un-emptied Trash as a finding, because on agent hosts that is often gigabytes.
Which user
Artifacts belong to the user who created them. Run Roomy as that user. To scan a different home as root, pass --home on the CLI or set scan.home in a policy. Roomy respects XDG_CACHE_HOME, XDG_DATA_HOME, HF_HOME, UV_CACHE_DIR, GOCACHE, TMPDIR and OPENCLAW_STATE_DIR when resolving catalog paths.
Containers
The image runs roomy as its entrypoint. Mount the home to clean and the policy:
docker run --rm -v /home/agent:/home/agent -v /etc/roomy:/etc/roomy:ro \
ghcr.io/tdermendjiev/roomy scan --home /home/agent --json
docker run -d --name roomy -p 9477:9477 -v /home/agent:/home/agent -v /etc/roomy:/etc/roomy:ro \
ghcr.io/tdermendjiev/roomy fleet serve --listen 0.0.0.0:9477
Set scan.home to the mounted path. Mount /var/run/docker.sock to enable the Docker category inside the container.
What differs from macOS
| Capability | macOS | Linux |
|---|---|---|
| Project walk, all project rules | ✓ | ✓ |
| AI tools, agent host, package caches, Docker, Conda, pyenv, global modules | ✓ | ✓ |
| Xcode, simulators, Homebrew | ✓ | — (nothing to find) |
| Trash | Finder | freedesktop |
| Quarantine | ✓ | ✓ (default) |
| Fleet and Hub | ✓ | ✓ |
For AI agents
An agent that hits ENOSPC mid-task, or is asked to free space, needs a tool that is structured, dry-run first, and incapable of deleting anything outside an explicit plan. That is the contract:
- Scan produces data.
roomy scan --jsonnever deletes. Every item has a stableid, atier, acategory,bytes,idleDaysand arestoreHint. - Plan is a file. Either
roomy scan --plan plan.json(safe tier by default) or a plan the agent writes by filtering the report'sitems. - Clean executes the plan and nothing else. Review-tier items need
--allow-caution; irreversible items need--allow-irreversible. An agent should not pass the latter without a human in the loop. - Disposal is recoverable. Trash on macOS, quarantine on Linux.
roomy restoreundoes. - Everything is audited. Every removal is appended to
~/.roomy/audit.jsonlwith the--actorname. Pass your tool name.
Recommended sequence
roomy doctor --json # optional: is roomy usable here?
roomy scan --json --quiet > /tmp/roomy-report.json # inspect bytesByTier, items
roomy scan --plan /tmp/roomy-plan.json --min-idle-days 7 --quiet
roomy clean --plan /tmp/roomy-plan.json --dry-run --json # show the human (or your log) what will happen
roomy clean --plan /tmp/roomy-plan.json --yes --json --actor "claude-code"
Read bytesByTier.safe first: if it is already enough, stop there. Only escalate to caution with explicit user approval, and never automate warning.
What the categories mean
project— artifacts inside repositories (node_modules,.venv,target/). Safe. Regenerate with the install command inrestoreHint.aiTools— the agents' own storage. Models and caches are safe; session history is irreversible (Claude Code projects, Codex sessions, Cline tasks, Cursor'sstate.vscdb).agentHost— leftovers on servers: OpenClaw build captures, crash dumps, Claude Code scratch dirs, stale Cursor agent builds, un-emptied Trash.globalCaches,python,docker,globalModules,xcode,homebrew— tool caches outside repositories.
A Claude Code skill and an AGENTS.md snippet with the sequence above ship in the repository. There is no MCP server yet; the CLI with --json is the integration surface.
Environment variables
| Variable | Effect |
|---|---|
ROOMY_POLICY | Default policy path for roomy fleet |
ROOMY_HUB_URL, ROOMY_HUB_TOKEN | Defaults for roomy hub status/hosts/…, and the agent token for a host when the policy has none |
ROOMY_HUB_DATA, ROOMY_HUB_AGENT_TOKEN, ROOMY_HUB_ADMIN_TOKEN | Defaults for roomy hub serve |
ROOMY_LICENSE | License token (alternative to a key file) |
ROOMY_QUARANTINE_DIR | Default quarantine directory |
ROOMY_HOSTNAME | Host label in reports, audit and metrics |
XDG_DATA_HOME | Where the freedesktop Trash lives on Linux |
Home · Mac app · Fleet · Hub · Release notes · Privacy · EULA · Source on GitHub
