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".
*, ?, [...]), 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 noor; 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.
2026-01-31T09:00:00Z), a plain date (2026-01-31), or a duration back from now (7d, 12h, 30m).
Choosing which snapshots to search
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.
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
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.Backed up unchanged, night after night
Backed up unchanged, night after night
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.
Edited over time
Edited over time
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.
Two files with identical content
Two files with identical content
~/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.One file in two places at once
One file in two places at once
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.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.
0, the same as cloudstic list on an empty repository and the same as find(1). Scripts tell the cases apart by reading matches:
-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/**/*.pdfis prefiltered on*.pdffirst, so paths are resolved only for the PDFs. -regexand patterns ending in**have no such prefilter, so every entry’s path must be reconstructed.findsays so on stderr rather than appearing to hang.
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
- cloudstic ls: List the file tree of one snapshot
- cloudstic restore: Restore files from a snapshot
- cloudstic list: List all snapshots
- cloudstic diff: Compare two snapshots