Repository Export
A repository export is one repository rendered as canonical NDJSON at an exact repository sequence: a header line, one row per item, and a trailer carrying the record count and a SHA-256 over the row bytes. Nothing is retained — the export is folded on request and streamed straight to you.
Export commands require unrestricted repo:read plus repo:checkpoint-read, the same gate a checkpoint download passes. A narrowed or deny-all read matcher is insufficient, including on a public repository. Any personal access token (PAT) missing repo:checkpoint-read must be reissued.
Export a repository
Section titled “Export a repository”wh repo export acme/widgets --output widgets.ndjson--output is required. It refuses to overwrite an existing path, so an interrupted export is never silently discarded. Use --output - to write the stream to stdout; that requires --format pretty, because JSON and JSONL output cannot carry export bytes.
The stream is verified as it arrives: the header must name the repository and mode you asked for, every row must decode canonically, and the closing trailer’s record count and digest must match what was actually read. Nothing is reported until the trailer proves the stream.
On success the command prints the repository, mode, delta base, repository sequence, record count, and digest. With --format json the same fields arrive as one object.
| Flag | Meaning |
|---|---|
--output PATH|- | Where to write the export. Required. |
--mode heads | Export mode. heads is the current version of every item, and the default. |
--since-repo-seq N | Export only what changed after sequence N. |
--at-repo-seq N | Pin the export to sequence N. Omit it and the server pins the current committed sequence and echoes it in the header. |
--async | Prepare the export server-side and print an export token, then exit. |
--async --wait | Prepare the export server-side and download it immediately when ready. |
--token TOKEN | Redeem a previously prepared export token and download the result. |
--resume | Continue an interrupted export already at --output PATH. |
Two exports at the same sequence produce byte-identical rows, so the digest is a stable identity for the export’s content and not merely a transport checksum.
Export a delta
Section titled “Export a delta”wh repo export acme/widgets --output delta.ndjson --since-repo-seq 120A delta carries only what changed after the named sequence, with deletions as tombstone rows ("active": false). Fold a delta onto a base by durable identity — active rows upsert, tombstones remove. The SDKs do exactly that for you.
A delta base older than the server’s epoch floor is refused, and the answer is a full export, not a retry.
Resume an interrupted export
Section titled “Resume an interrupted export”An export interrupted mid-stream keeps the verified rows it had already written:
wh repo export acme/widgets --output widgets.ndjson --resumeResume reads the partial file back, replays it through the same verifier, and asks the server to continue after the last whole row at the sequence the original header pinned. There is no sidecar state file: the partial export describes itself.
Because the file supplies the window, --resume refuses --mode, --since-repo-seq, and --at-repo-seq rather than silently ignoring them, and it refuses a file whose header names a different repository. It also needs a real file — stdout cannot be continued.
Queue a large export
Section titled “Queue a large export”One synchronous export streams per organization at a time. A second concurrent request is refused, and the CLI names the overflow path.
Use --async to prepare the export server-side without taking the synchronous slot. The command prints an export token and exits immediately:
wh repo export acme/widgets --asyncTo download the result in the same invocation, add --wait:
wh repo export acme/widgets --output widgets.ndjson --async --waitTo redeem a token printed by an earlier --async run:
wh repo export acme/widgets --output widgets.ndjson --token <TOKEN>--async and --resume are mutually exclusive: a resume continues an already-pinned stream, not a new job.
Verify a saved export
Section titled “Verify a saved export”wh repo verify widgets.ndjsonverify re-reads a saved export and checks framing, canonicality, record count, sequence agreement, and the content digest. It resolves no profile, authenticates nothing, and contacts no server — it reads only the named local file.
Distinct failures are reported distinctly: a mangled row, a miscounted trailer, a wrong digest, and a stream that never reached its trailer are four different messages.
Verify before you convert an export to another format. Parquet and CSV carry no trailer, so once the NDJSON is transformed the digest can no longer be checked against it.
Failures
Section titled “Failures”| Condition | The CLI says |
|---|---|
| The delta base has expired | The delta base has expired; run a full export. |
| Another export is already streaming for the organization | Another repository export is already streaming for this organization. Retry with --async. |
--mode ops is not enabled on the deployment | Export mode “ops” is not enabled on this deployment. |
--async is not enabled on the deployment | Asynchronous repository export is not enabled on this deployment. |
| The queued export job failed | The repository export job failed. Re-running the command starts a new job. |
Limits
Section titled “Limits”--asyncmoves the fold off the request path and prints an export token you can redeem later with--token TOKEN. Add--waitto download immediately in the same invocation.- Exports are not retained. There is no listing, no re-download by sequence, and no identity to hand to another caller.
- There is no cancellation command. Abandon the stream instead.
- Verification cannot be skipped. The digest is computed over lines that are being decoded anyway.