On this page
Space
space is the fleet capability for understanding and reclaiming storage on a declared local target. It combines filesystem capacity, free bytes and policy watermarks, free memory and swap, inventory roots, build-cache verdicts, the janitor lock, and the janitor's last pass in one report. Reclamation is a preview unless the operator explicitly supplies --apply and a reason.
Invocations
stado space report TARGET [--json]- Reads one combined report from the declared target.
--jsonemits the report as a JSON object.
stado space reclaim TARGET [--stage STAGE]... [--dry-run|--apply] [--reason TEXT] [--json]- With no
--stage, reads and selects every stage in the compiled declaration. - Repeat
--stageto select a subset. - Omitting both mode flags is a dry run.
--dry-runstates that choice explicitly.--applyperforms deletion and requires a non-empty--reason. - An applied run appends its reason, actor, selected stage outcomes, freed-space readings, and timestamp to
.stado/audit/host-reclaim.jsonlon the target, beside the target state that changed. --jsonemits the reclamation report as a JSON object.
- With no
stado space file remove TARGET PATH [--json]- Removes one regular file only from a Stado-managed area on the declared target.
stado space file retire TARGET PATH --product PRODUCT [--dry-run] [--transaction ID] [--expected-sha256 SHA256] [--expected-size BYTES] [--expected-mode MODE] [--json]- Archives an obsolete executable or launchd declaration without deleting its bytes. Optional expected values are preconditions.
--dry-runreports the guarded move without performing it.
- Archives an obsolete executable or launchd declaration without deleting its bytes. Optional expected values are preconditions.
stado space relocate TARGET --namespace NAMESPACE --from-prefix PREFIX [--to-prefix PREFIX] [--store-root PATH] [--dry-run|--apply] [--limit COUNT] [--json]- Relocates object-store keys on the target holding their bytes. The default is a dry run;
--applycommits the relocation.--limit 0means no item limit.
- Relocates object-store keys on the target holding their bytes. The default is a dry run;
stado space cleaners list TARGET [--json]- Lists implemented cleaners, their declarations and minimum supported versions.
installed_stadois read from the target's executable, not copied from its desiredmanaged_versions.installed_read_errorrecords a missing observation andpolicy_modedistinguishes declaration from enabled deletion.
- Lists implemented cleaners, their declarations and minimum supported versions.
stado space cleaners declare TARGET --cleaner NAME [--root PATH] [--min-age-seconds N] [--keep-newest N] [--allow-missing-upload-proof BOOL] [--json]- Adds or edits one cleaner through the canonical registry's validated compare-and-swap. Omitted fields on an existing cleaner are preserved. A newly declared cleaner receives its catalogue retention floor when no age is supplied.
--rootis absolute or starts with~/. Root overrides for Hugging Face and queue-workdir cleanup require the additional version reported byroot_override_since; their roots must remain beneath the host's home. Backup root overrides require the version carrying primary-file identity protection.--keep-newestbelongs torelease_storeand must retain at least one newest version. Recording cleanup consumes--allow-missing-upload-proof; true explicitly permits deletion without that proof, subject to its remaining guards.- A host with no policy starts from the reporting default. Declaring a cleaner never silently changes the policy mode to
enforce.
stado space cleaners remove TARGET --cleaner NAME [--json]- Removes only that cleaner. A second removal is refused; so is removing the last cleaner from an enforcing policy.
Declarations read
The capability's stage vocabulary is compiled from stado-rs/data/space.json with include_str!. The reclaim_stages array currently declares, in execution order:
registry_cleanupbuild_scratchqueue_workdirsforeign_home_treesdelivered_treesrebuildable_cacheschromium_cloneslocal_apfs_snapshotsrunner_work_trees
Both space report and space reclaim read that same array. A new stage name is a declaration row rather than a new command or match arm.
Target identity, transport, platform, disk watermarks, and janitor cleaner policy come from the canonical registry target. Build-cache reporting specifically reads targets[].disk_cleanup.cleaners.build_caches.root and .min_age_seconds. Reclamation's registry_cleanup stage invokes the target janitor, so its cleaner set and limits remain the registry's targets[].disk_cleanup declaration. File operations and relocation use the target's canonical registry identity and managed storage declarations rather than accepting an undeclared host channel.
A target that declares no disk_cleanup at all is read against the reporting default rather than refused: mode: report, one build_caches cleaner, and the account's own home as the root (DiskCleanupPolicy::reporting_default, stado-rs/src/targets/policies.rs). That is the policy the janitor has always measured an undeclared host against. space report and the build-cache reader itself (stado-rs/src/deploy/host_build_caches/read.rs) refused that same host, so a machine nobody had declared was being reported on by the janitor and was unreadable to the two commands an operator would ask — the reader and the janitor disagreed about a host neither of them had been told anything about. They now read the one default.
Build-cache entries preserve these safety verdicts in the combined report: no-cache-tags when the declared root contains no cache tag, root-protected when the root itself is tagged, and scan-failed when the root cannot be safely scanned.
What the report answers about pressure
The report separates free space, scan coverage and deletion eligibility. A directory inside a cleaner's root is not necessarily removable: the cleaner may retain it because of age, active work, release references, upload proof or a missing independent primary copy.
inventory[].bytes preserves the host's du count in bytes; size_gb is its rounded display value. Coverage uses the byte count rather than reconstructing bytes from the rounded number. The inventory is bounded and is not a measurement of how many physical blocks deletion will recover; snapshots, shared extents and concurrent writes can change that result.
The coverage object contains:
need_bytes: the distance belowtarget_free_gb;deficit_bytes: the distance belowlow_free_gb. They are null when the required watermark or free-space reading is absent.covered: the explicitly listed stage roots for this platform, with their measured byte counts ormeasured: false.cleaner_scopes: each resolved cleaner root, whether it is declared, and its measured size. A declared override replaces that cleaner's default. Chromium's default root comes from the target's own macOS temporary-container reading, not the operator's computer.uncovered: measured rows outside the explicitly listed stage roots.mechanismnames a cleaner whose root contains the row;mechanism_declaredsays whether this host declares that cleaner. These fields describe scope, not whether a pass ran or a file may be deleted.exclusive_of_measured_children: a row with this flag counts only the parent's remainder after its separately measured children. A cleaner rooted insidelocal-storagetherefore cannot claim unrelated objects beside its own subtree.covered_bytes,cleaner_bytesandunswept_bytes: non-overlapping totals of the measured rows in stage roots, in declared cleaner roots outside them, and outside both. Totals are computed before the display limit;uncovered_rowsretains the full row count.unarmed: implemented cleaners with a measured root which this host has not declared. The record names the root and its measured bytes, not the size of an enclosing directory.space cleaners listobserves installed support before a declaration is written.reclaimable_bytes: null. Coverage alone cannot establish recoverable capacity.
verdict |
Meaning |
|---|---|
undeclared |
The target declares no disk-cleanup policy and no explicit free-space watermark. |
holds |
The host is at or above its low watermark; this does not assert that it has reached the higher target. |
unmeasured |
A policy is declared but a required free-space or inventory measurement is unavailable. |
declared |
The host is below its low watermark and the measured rows lie within declared scan roots. Deletion eligibility remains a separate question. |
uncovered |
The host is below its low watermark and some measured bytes lie outside declared scan roots. This does not prove that cleanup elsewhere cannot reach the target. |
cleanup_state.report retains the complete recorded janitor pass, and coverage.janitor.report carries it beside the current shortfall. It includes per-cleaner scanned, eligible and deleted counts, exact skipped reasons, exhausted caps, errors, writer identity and the pass's own free-space readings. Missing pass evidence is not replaced with zero counters.
The text report prints these results after the janitor's timestamp and outcome. A row begins with the declared cleaner name, unarmed:<name>, or uncovered; an exclusive parent remainder is marked explicitly. Neither cap_reached nor a large directory is reported as proof that another pass will succeed or cannot succeed.
Progress across bounded passes
The backup-twin cleaner persists backup_twins_cursor with its remaining directory frontier and position inside the current directory. A later pass continues past retained files instead of scanning the same prefix forever. Changing the policy or root invalidates that checkpoint, and a completed traversal clears it. The checkpoint never authorizes deletion: each candidate is checked again and both files are hashed in the pass that removes the replica.
If the selected replica and primary identify the same file, the cleaner retains it with same_file_as_primary. A root override cannot turn the primary into a duplicate of itself. New backup-root declarations require the installed Stado version named by root_override_since.
Locations a pass never opens
On macOS the build-cache walk refuses ~/Pictures, ~/Music, ~/Movies, ~/.Trash, ~/Library/Mobile Documents and ~/Library/CloudStorage before it opens them, and counts each refusal as privacy_protected in that cleaner's skipped map. Opening them is the cost: the media folders sit behind a macOS privacy consent dialog, so an unattended pass walking $HOME — this cleaner's default root — makes the operating system ask the person at the keyboard for the photo library, and the two cloud roots hold placeholders whose contents download when a reader touches them. No build tool writes CACHEDIR.TAG inside any of them, so the refusal gives up no reclaimable bytes.
~/Documents, ~/Desktop and ~/Downloads are consent-gated on macOS as well and remain in scope, because real build trees live in them; those are one grant decision per signed binary, not a recurring dialog. A tagged directory that contains one of the refused roots is queued rather than deleted, so a cache tag written above a photo library never authorizes removing it.
Refusal sentences
Reclamation and build-cache refusals include:
stage '<stage>' is not declared; add it to stado-rs/data/space.json reclaim_stagesstado-rs/data/space.json declares no reclaim stages; add at least one to reclaim_stagesstado-rs/data/space.json reclaim stage '<stage>' declares no description; add it to reclaim_stages<target> declares no eligible space reclamation stage; add it to stado-rs/data/space.json reclaim_stages<target> declares no build cache cleaner; add it to registry targets[].disk_cleanup.cleaners.build_caches<target> declares build cache root "<root>" outside an absolute or home-relative path; fix registry targets[].disk_cleanup.cleaners.build_caches.rootspace reclaim --apply removes files and needs --reason <text>; the reason is appended to the target's own audit log beside the state it changed. Run without --apply to preview the declared stages
Cleaner writes also retain their specific refusal:
<name> is not a cleaner this product implements; declare one of: backup_twins, build_caches, chromium_clones, huggingface_cache, queue_workdirs, release_store, weles_recordingscannot verify cleaner support on <target>: <observed inventory error><target> reports installed stado <version>; <cleaner> requires at least <since>; no policy was changed<cleaner> root overrides require stado <required>; <target> reports <installed>; no policy was changedcleaner root must be absolute or begin with ~/<target> declares no cleaner <name>
Invalid age or retention fields are refused by the canonical registry validator before the compare-and-swap. Withdrawing the last cleaner from an enforcing policy is also refused. An unavailable installed version is not inferred from managed_versions: that field declares the desired version and is not installation evidence.
A target with no disk-cleanup policy is still measured against the reporting default. Missing configuration and an invalid existing declaration are different states. Target resolution and guarded file operations retain their target, path, expected value and actual error.
Replaced verbs
The following former host invocations are deleted, not retained as aliases:
stado host diskstado host disk-cleanupstado host cleanupstado host reclaimstado host build-caches reportstado host build-caches prunestado host object-relocatestado host remove-filestado host retire-file
Stado Desktop
The host inspector's Space section reads space report TARGET --json through the selected endpoint's native POST /api/operator/run API. It shows filesystem and memory readings, policy watermarks, scan roots, exclusive parent remainders, cleaner names and the recorded janitor's skipped reasons, limits and errors. The complete command receipt remains available, including failures and incomplete inventory. Desktop does not infer removable bytes from directory sizes.
The Janitor cleaners section uses the same API to list, declare, edit and withdraw cleaners. It displays the observed installed Stado version, the policy mode and the actual root override. The policy editor exposes root, minimum age, release retention count and the recording upload-proof option where applicable. A save uses the CLI's validated registry write and reads the declaration back; omitted fields are preserved. Declaring a cleaner does not silently change a reporting policy into an enforcing one.
Requests retain the selected host and endpoint. An answer from a previous selection is not shown as the new host's data, and a failed command keeps its returned stdout, stderr and refusal. The GUI does not launch a local CLI subprocess for these operations.
The existing reclamation confirmation sends space reclaim TARGET --dry-run --json for a preview and space reclaim TARGET --apply --reason TEXT --json for the reviewed mutation. Both CLI and Desktop enter the same product implementation.
Explicit working-directory cleanup
stado workdirs [--apply] [--json] operates on the account running the command, not a selected fleet target.
Without --apply it inventories every directory immediately below ~/.stado/work.
With --apply it removes all of those directories, including jobs, runs, run-signals and active contents.
There is no age rule, live-job exception or recurring deletion timer in this command.
Do not apply it while work you intend to keep uses that root.
Regular files and symbolic links directly at the root stay. Links inside removed trees are unlinked without following them.
stado workdirs --json
stado workdirs --apply --json
The versioned report records directories, removed, failed and the final remainingDirectories.
Each failure names its path, attempted operation and operating-system error.
A missing root is an empty result; a symlinked, unreadable or non-directory root is a failure, not an empty directory.
The command checks the root through non-following directory descriptors and never traverses a different filesystem.
Both text and JSON modes exit nonzero when an operation fails or a directory remains after apply.
The refusal is working directory cleanup incomplete: <count> failure(s), <count> directory(ies) remain.
A concurrent writer can recreate a directory during removal; that directory is reported rather than hidden.
apparentBytes and apparentBytesRemoved count file lengths, not reclaimed disk blocks.
freeBytesBefore and freeBytesAfter are separate filesystem observations.
Shared APFS extents, snapshots, open files and concurrent writes can make those measurements differ.
The command does not delete snapshots and does not infer their retained size.
If a removed directory immediately returns, first identify the program recreating it.
stado service reap --host TARGET --command PROGRAM previews only the named program under the product, service and scratch roots.
Its --apply form sends SIGTERM only when no registry-declared unit owns the process or an ancestor.
The JSON receipt includes the actual scanned_roots, enumeration in examined, protected PIDs and each process outcome.
Both text and JSON return failure if a selected process remains running.
Retire a confirmed obsolete writer before repeating workdirs --apply; do not signal unrelated services.
Settings → Working directories on the Stado API host exposes the same preview, root, destructive confirmation and complete receipt.
It sends an argv request through the selected endpoint's POST /api/operator/run; it does not spawn a local CLI.
The request carries X-Stado-Action: operator-command and the endpoint's existing operator authorization.
Apply requires confirmation: RUN_MUTATION; a missing confirmation returns HTTP 403 with
mutating commands require explicit RUN_MUTATION confirmation.
The selected endpoint, not a host highlighted elsewhere, owns the filesystem being changed.
Incomplete inventory is reported
The disk reader retains its inexpensive filesystem, memory and janitor observations before attempting the whole-tree inventory.
The inventory has a separate default bound of 900 seconds.
STADO_INVENTORY_BUDGET_SECONDS can change it to a positive whole number; malformed or zero values are refused with
STADO_INVENTORY_BUDGET_SECONDS must be a positive whole number of seconds.
When that inventory fails, JSON includes inventory_incomplete with the actual command error.
Text output prints inventory incomplete: followed by the same reason.
Hosts → Space shows an Inventory incomplete warning and retains the complete API receipt.
Missing paths were not checked: an empty coverage list in this state does not prove that the disk is covered.
The Desktop API allows space and workdirs operations up to 1200 seconds, independently of the ordinary 300-second command bound.
Source: this website