Skip to main content
Locate a file across every snapshot in the repository, without knowing which snapshot holds it. Every other read command takes a snapshot as input. find returns one as output, which is what makes it useful for the case you actually hit: recovering a file that is no longer in the latest snapshot. If you know the file is in the latest snapshot, cloudstic ls already finds it.

Usage

find searches every snapshot by default. That is affordable because snapshots share structure: within a source lineage, Cloudstic walks the oldest snapshot once and then diffs consecutive ones, so an unchanged directory costs a single pointer comparison rather than another full walk.

Patterns

The positional pattern is shorthand, and its shape decides what it matches:
  • No / — matches the file’s basename. "vault.kdbx" is the same as -name "vault.kdbx".
  • Contains / — matches the full path, relative to the source root. "Documents/report.pdf" is the same as -path "Documents/report.pdf".
That split is not cosmetic. A name is already on the file’s metadata object, while a path has to be reconstructed by walking up the parent chain, so a name query is meaningfully cheaper. Glob syntax is the standard one per path segment (*, ?, [...]), plus ** for “zero or more path segments”:
* never crosses a /. Use ** when you mean “at any depth”.

Matching options

All options given must match. There is no or; run two queries and combine the results yourself.
string
Positional pattern. Matches the basename when it contains no /, the full path when it does.
string
Match the file’s basename against a glob. The explicit form of a /-free positional pattern.
string
Match the file’s full path against a glob. ** spans any number of directories.
string
Match the file’s full path against an RE2 regular expression. Unanchored, like grep.
boolean
default:"false"
Make -name, -path, and -regex case-insensitive. This affects matching only; it says nothing about the case sensitivity of the filesystem the files came from.
string
Match one source file ID. The ID is stable across renames and moves within a source, so this is the “show me this file’s whole history” query.
string
Match files whose content has this SHA-256. Answers “where else do I store these exact bytes?”
string
Match one exact metadata object, as filemeta/<hash> or a bare hash.
string
Match only files (f) or only folders (d).
string
Match by size, using find(1) syntax: +10M (at least), -1k (at most), 4096 (exactly). Suffixes are binary: k, M, G, T.
string
Match files modified after a time.
string
Match files modified before a time.
-id, -content-hash, and -ref compare against values the repository stores verbatim, so they are the cheapest queries available and the ones to reach for when you already know what you are looking for.
Times accept RFC3339 (2026-01-31T09:00:00Z), a plain date (2026-01-31), or a duration back from now (7d, 12h, 30m).
string
Search only this snapshot. Repeatable, and accepts a full hash, a unique prefix, or latest.
string
Search only snapshots of this source URI, for example local:./Documents.
string
Search only snapshots carrying this tag. Repeatable.
integer
Search only the N newest selected snapshots.
string
Search only snapshots created at or after a time.
string
Search only snapshots created at or before a time.
-since and -until select snapshots by when the backup ran. -newer and -older select files by their modification time. The two are easy to confuse and do genuinely different things.

Output options

boolean
default:"false"
Group results by content hash instead of by file. Changes grouping only, never which entries matched.
integer
default:"1000"
Stop accumulating after N matching files. Scanning continues to completion, so the reported counters stay accurate.
boolean
default:"false"
Write the full result to stdout as JSON.
boolean
default:"false"
Walk every snapshot in full instead of diffing consecutive ones. Much slower; it exists so the fast path can be checked against a simple one.

Reading the results

A result is a file, carrying its versions, each carrying the snapshots it appears in. The trailing hint spells out the restore, because running find almost always means restore comes next.

Why you do not get thousands of rows

A repository holds the same file many times over, and each kind of repetition is collapsed differently.
A file backed up nightly and never edited has one metadata object shared by all thirty snapshots. It is reported as one version credited with thirty snapshots, not thirty identical rows.This is not a display convenience. The underlying references are literally equal, so one row is the accurate reading of what the repository contains.
Each edit produces a new version. You get one match with several versions, newest first, each with its own snapshot range.This is the view you want when recovering a file: “which version do I want?” is the real question, and a version with a date range is the answer to it.
~/Documents/report.pdf and ~/Backup/report.pdf may hold identical bytes, but they are different files and stay separate matches. Collapsing them would be wrong: restoring one is not restoring the other.When finding duplicates is the goal, -by-content regroups the same matches by content hash.
A Google Drive file can live in two folders simultaneously, and hard links raise the same question. Every path is listed on its own line; none is silently picked.

Renames

Results group by file ID rather than by name, and the name lives on the version. A renamed file is therefore one match whose versions carry different names — a rename history, as a consequence of how grouping works rather than a separate feature. One consequence is worth stating plainly: a name query matches only the versions bearing that name.
Pulling in the post-rename versions would make the result set depend on grouping, which is how a search tool becomes unpredictable.

Examples

Machine-readable output

-json emits the full result. The schema is the same whether or not -by-content is set; only the grouping differs.
Finding nothing exits 0, the same as cloudstic list on an empty repository and the same as find(1). Scripts tell the cases apart by reading matches:
Warnings about how the search ran are written to stderr, so they never contaminate a -json pipeline. They also appear in the JSON payload as a warnings field.

Performance

The cost of searching a whole source lineage is roughly one full walk plus the repository’s actual churn — not one walk per snapshot. Within a lineage, Cloudstic walks the oldest selected snapshot in full and then diffs consecutive snapshots. Because the metadata tree is persistent, an unchanged subtree keeps its reference across snapshots, so the diff descends only where something actually changed. Metadata objects are content-addressed and immutable, so one already evaluated is never fetched or evaluated again — including across sources, which is what makes an identical file backed up from two machines cost one evaluation rather than two. Two things to know about query cost:
  • A basename pattern is cheaper than a path pattern. Paths are reconstructed rather than stored. A pattern like Documents/**/*.pdf is prefiltered on *.pdf first, so paths are resolved only for the PDFs.
  • -regex and patterns ending in ** have no such prefilter, so every entry’s path must be reconstructed. find says so on stderr rather than appearing to hang.
Use -verbose on a large repository to watch metadata objects being read.

Limitations

find matches metadata, not content. There is no full-text search: matching inside file contents would mean fetching, decrypting, and decompressing every chunk in the repository, which is a different feature with a different cost model. There is also no repository-side search index, by design. A stale or partially written index that silently returns no matches is a data-loss-shaped bug wearing a search feature’s clothing, and the delta scan gets acceptable performance without one.

Encryption

find requires an unlocked repository. File metadata is encrypted at rest, so there is no metadata-only access path, and no new exposure: anyone who can run find can already run ls and restore.
Filenames are sensitive even when contents are not. find keeps matched paths out of default log output.

See Also