Skip to content

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.

Terminal window
wh repo export acme/widgets --output widgets.ndjson

--output is required for synchronous, --async --wait, and --token download paths. 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.

When --async is used without --wait, --output must be omitted — the command prints an export token and exits immediately without downloading anything.

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.

FlagMeaning
--output PATH|-Where to write the export. Required for synchronous, --async --wait, and --token download paths. Must be omitted when using --async without --wait.
--mode heads|opsExport mode. heads, the default, is the current version of every item. ops is not enabled yet; asking for it fails with the error listed under Failures.
--since-repo-seq NExport only what changed after sequence N.
--at-repo-seq NPin the export to sequence N. Omit it and the server pins the current committed sequence and echoes it in the header.
--asyncPrepare the export server-side and print an export token, then exit. Do not supply --output with this flag unless also using --wait.
--async --waitPrepare the export server-side and download it immediately when ready. --output is required.
--token TOKENRedeem a previously prepared export token and download the result. --output is required.
--resumeContinue 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.

Every line in the export body (excluding the header and trailer) is a row object with a kind field that identifies its type. Export format version 6 defines two row kinds:

kindMeaning
"shape"A shape definition row.
"thing"A thing row, including any assertion metadata.

Every active kind: "thing" row carries a shapes field. In export format version 6, shapes is an array of pinned Shape certificates identifying the shapes the row conformed to at the export sequence. Each entry is a Shape name and version, such as Facility@v2; a Shape from another repository keeps its repository prefix, such as wh:acme/world/Arc@v1. A full export carries every version of every Shape in the repository, retracted Shapes included, so a certificate for one of the repository’s own Shapes always matches a Shape row in the same file. The array is empty ("shapes": []) when the row conforms to no shapes. Tombstone rows ("active": false) do not carry a shapes field.

Assertions are surfaced as kind: "thing" rows. In addition to the standard thing fields, an assertion row carries two paired fields:

FieldTypeMeaning
aboutobjectA reference object identifying the shape or thing this assertion is about. Contains wref (string) and durableId (string), and an optional v field.
affirmedarrayAn array of reference objects (each with wref, durableId, and optional v) identifying the versions this assertion affirms. Each entry is a specific version of a thing or Shape, and v is its version number; an entry whose target was removed has its durable ID as its wref and no v.

Both fields are present together or absent together. A kind: "thing" row without about and affirmed is a plain thing, not an assertion.

Terminal window
wh repo export acme/widgets --output delta.ndjson --since-repo-seq 120

A 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.

An export interrupted mid-stream keeps the verified rows it had already written:

Terminal window
wh repo export acme/widgets --output widgets.ndjson --resume

Resume 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.

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 — do not supply --output in this form:

Terminal window
wh repo export acme/widgets --async

To download the result in the same invocation, add --wait:

Terminal window
wh repo export acme/widgets --output widgets.ndjson --async --wait

To redeem a token printed by an earlier --async run:

Terminal window
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.

Terminal window
wh repo verify widgets.ndjson

verify 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.

ConditionThe CLI says
The delta base has expiredThe delta base has expired; run a full export.
Another export is already streaming for the organizationAnother repository export is already streaming for this organization. Retry with --async --wait to queue this export instead.
--mode ops is not enabled on the deploymentExport mode “ops” is not enabled on this deployment.
--async is not enabled on the deploymentAsynchronous repository export is not enabled on this deployment.
The queued export job failedThe repository export job failed. Re-running the command starts a new job.
  • --async prepares the export in the background and prints an export token you can redeem later with --token TOKEN. Add --wait to download immediately in the same invocation.
  • Exports are not retained. There is no listing and no re-download by sequence. You can hand an export token from --async to another process or machine. Whoever redeems it with --token needs both repo:checkpoint-read and unrestricted repo:read on the repository; read access limited to some things is refused.
  • There is no cancellation command. Abandon the stream instead.
  • Verification cannot be skipped. The digest is computed over lines that are being decoded anyway.