Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

CLI Guide

📌 The exhaustive command/flag reference is generated from the code: cli-reference.md — rendered from the clap definitions (the same source as --help), so it cannot drift and needs no manual verification. This page is the guide: the same commands with worked examples, output samples, and the why. For the guaranteed-current flag list of any command, trust the generated reference (or run rivet <command> --help).

Machine-readable output. Most commands emit JSON via a boolean --json flag (run, check, doctor, metrics, state …); validate (and the reconcile / plan report writers) instead take --format json. This split is a known inconsistency to be unified in a future release — until then, pass --help to confirm which idiom a given command uses.

Global

rivet [--json-errors] [COMMAND] [OPTIONS]
rivet --version       # print version
rivet --help          # show help
FlagDescription
--json-errorsOutput errors as {"error":"..."} JSON to stderr instead of plain text. Applies to all subcommands. Useful for machine-readable orchestration and CI pipelines.
rivet --json-errors run --config rivet.yaml
rivet run --config rivet.yaml --json-errors   # global flag accepted in any position

rivet run

Run export jobs defined in a config file.

rivet run --config <PATH> [OPTIONS]
FlagShortTypeDescription
--config-cstringPath to YAML config file (required)
--export-estringRun only a specific export by name
--validateboolValidate output file row count after writing, then verify the manifest and every part at the destination. A failed verification fails the run’s exit as rivet validate would on that prefix: RIVET_VALIDATE_FAILED (exit 3) when a check found the destination wrong, RIVET_VALIDATE_UNVERIFIED (exit 1) when it could not be read back. The export itself completed: its parts, manifest and _SUCCESS stay
--reconcileboolRun COUNT(*) on source query and compare with exported rows
--resumeboolResume an in-progress chunked export. Exits non-zero with an actionable message if no in-progress checkpoint exists — run without --resume to start fresh, or rivet state reset-chunks to clear a stuck run
--forceboolOverride safety gates that would otherwise refuse the run. Today: with --resume, goes past the refusal to resume into a destination prefix whose _SUCCESS marker is already present (RIVET_DEST_ALREADY_COMPLETE, exit 5, ADR-0012 M8): the run continues an interrupted run of the export if one is recorded, else it runs as a plain run does. New parts land beside the old ones, nothing is overwritten, and manifest.json then describes only the new run
--parallel-exportsboolRun the config’s exports concurrently, at most 16 at once; a CDC export run alone also takes its pending baseline snapshots at most 16 at once
--parallel-export-processesboolRun each export as a separate child process
--summary-outputPATHWrite run aggregate to this file as JSON
--jsonboolPrint run aggregate to stdout as JSON after the run
--param-pKEY=VALUEQuery parameter (repeatable). Substitutes ${key} in queries

Examples

# Basic run
rivet run -c my_export.yaml

# Run with validation and reconciliation
rivet run -c my_export.yaml --validate --reconcile

# Run a single export
rivet run -c my_export.yaml -e orders_daily

# Resume interrupted chunked export
rivet run -c my_export.yaml -e big_table --resume

# Parameterized query
rivet run -c my_export.yaml -p region=us-east -p year=2026

# Parallel exports (all at once)
rivet run -c my_export.yaml --parallel-exports

# Parallel exports — one OS process per export, parent-side cards UI
rivet run -c my_export.yaml --parallel-export-processes

--parallel-export-processes — one card per export

--parallel-exports runs the exports in the same Rivet process on up to 16 worker threads. That keeps logs simple, but every export shares the same source connection pool / global allocator. A panic in one export is caught and reported as that export’s failure while the others finish (release builds unwind; the release-min profile aborts instead).

--parallel-export-processes instead spawns one rivet child process per export — full memory and connection isolation, no shared allocator. The parent process owns the screen and renders one card per export with a live progress bar, ETA, row count, and elapsed time. When a child finishes, the progress bar is replaced in place with the export’s final metrics, so the on-screen card becomes a self-contained per-export summary; below the cards a single aggregated Run summary block prints once for the whole run.

▸ orders            chunked   11/20 chunks   1.1M rows  8.7K r/s   2m 06.0s  ETA 1m 43.1s

One card line per export (▸ running, ✓ finished), redrawn in place; the children’s verbose per-export output goes to a timestamped log file beside the config.

Children emit structured NDJSON events (Started, ProgressInit, Progress, Finished) on stdout via the RIVET_IPC_EVENTS=1 env var; the parent multiplexes them into the cards UI. If a child crashes without a Finished event, its card is marked failed with a synthetic warning so a silent crash never leaves the run looking healthy.


rivet cdc

Stream log-based change data capture directly (without a config). The engine is chosen from the URL scheme — mysql:// (binlog) / postgresql:// (logical slot) / sqlserver:// (change tables) / mongodb:// (change stream). Emits NDJSON to stdout by default, or typed Parquet/CSV with --output (--output requires exactly one --table — the schema is resolved from the source); --checkpoint persists a resume position.

rivet cdc --source-env DATABASE_URL --table orders                 # NDJSON to stdout
rivet cdc --source-env DATABASE_URL --table orders --output ./cdc --format parquet --checkpoint ./o.ckpt

The full reference — per-engine prerequisites, --slot / --capture-instance / --server-id, --stream (opt into continuous; bounded is the default), the config-driven rivet run + mode: cdc path (the fuller path, all four engines incl. MongoDB), and the failure/recovery playbook — is in cdc.md.


rivet plan

Generate a sealed execution plan artifact — no data is exported.

rivet plan runs preflight analysis (row estimate, index check, sparsity), computes chunk boundaries for chunked exports, snapshots the current cursor for incremental exports, and writes everything to a PlanArtifact JSON file. The artifact can be reviewed, committed, stored as a CI artifact, or passed to rivet apply.

rivet plan writes a plan only for a source it could read. When the preflight fails (no connection, a login the source rejects, a table or MongoDB collection that does not exist), the command ends with RIVET_PLAN_SOURCE_UNREADABLE and writes no plan file for any export: exit 1, or exit 2 when the cause is transient (a dropped or refused connection).

rivet plan --config <PATH> [OPTIONS]
FlagShortTypeDefaultDescription
--config-cstring—Path to YAML config file (required)
--export-estringallPlan only a specific export
--param-pKEY=VALUE—Query parameter (repeatable)
--output-ostringstdoutWrite plan JSON to this file
--formatpretty|jsonprettypretty prints a human summary; json writes the full artifact
--dateYYYY-MM-DD—Freeze {date} in the destination to this day. Without it the sealed plan keeps {date} as written and rivet apply resolves it to the UTC day it runs on, as rivet run does

Examples

# Human-readable summary (no file written)
rivet plan -c rivet.yaml

# Write full JSON artifact to a file
rivet plan -c rivet.yaml --format json --output plan.json

# Plan a single export
rivet plan -c rivet.yaml -e orders --format json -o orders_plan.json

Pretty output (example)

  Plan ID  : a1b2c3d4e5f6...
  Created  : 2026-04-14 10:00:00 UTC
  Expires  : 2026-04-15 10:00:00 UTC
  Export   : orders
  Strategy : chunked
  Chunks   : 42
  Row est. : ~2,100,000
  Verdict  : Acceptable
  Profile  : balanced
  Warnings :
    • sparse id range: ~12% fill
  Resources:
    Batch size   :  10,000 rows
    Batch memory : ~2 MB (narrow) – ~95 MB (wide)
    RSS guard    : 4,096 MB
    Throttle     : 50 ms between batches
  Output   : local → ./out
  Format   : parquet + zstd

The Resources section shows:

LineMeaning
Batch sizeRows fetched per query. adaptive if batch_size_memory_mb is set.
Batch memoryEstimated range: narrow (~200 B/row) to wide (~10 KB/row) tables.
RSS guardProcess-level RSS threshold. Fetching pauses if exceeded (0 = disabled).
ThrottleDelay between batches to reduce source load (omitted when 0).
⚠ Wide tables may use…Shown when the upper bound exceeds 128 MB/batch — consider batch_size_memory_mb or a lower batch_size.

Memory estimate methodology — advisory only

The memory estimate in rivet plan is a heuristic, not a guarantee. Treat it as a planning signal, not a hard prediction.

rivet plan does not sample the table. It computes the batch memory range using two fixed assumptions:

  • Narrow bound — 200 B per row (all INTEGER / BIGINT / TIMESTAMPTZ columns)
  • Wide bound — 10 KB per row (all TEXT / JSONB / BYTEA columns)

For most real tables the actual per-row size falls between these two bounds. The narrow bound is a reliable floor for numeric-heavy schemas; the wide bound is a reliable ceiling for text-heavy schemas.

What the estimate does not capture:

FactorEffect on actual RSS
Highly variable TEXT/BLOB valuesActual batches can be 2–10× the wide estimate
Sparse nullable columnsActual batches will be below the narrow estimate
Compression buffers in the Parquet writerAdds 50–200 MB on top of the Arrow batch size
Tokio runtime, connection pool, jemallocAdds 50–150 MB baseline overhead

How to get a precise number: run rivet run once with RUST_LOG=info against a representative sample, then check the peak_rss in the logged summary or in rivet metrics. That measured value from your actual data is more reliable than any pre-run estimate.

Planned enhancement: a future rivet plan --sample N flag will query up to N rows to compute a data-driven row-width estimate. This will narrow the uncertainty for variable-width schemas without a full table scan.

Plan artifact structure

The JSON artifact (--format json) contains:

{
  "rivet_version": "0.18.0",
  "plan_id": "a1b2c3d4...",
  "created_at": "2026-04-14T10:00:00Z",
  "expires_at": "2026-04-15T10:00:00Z",
  "export_name": "orders",
  "strategy": "chunked",
  "plan_fingerprint": "0123456789abcdef",
  "resolved_plan": { ... },
  "computed": {
    "chunk_ranges": [[1, 50000], [50001, 100000], "..."],
    "chunk_count": 42,
    "cursor_snapshot": null,
    "row_estimate": 2100000
  },
  "diagnostics": {
    "verdict": "Acceptable",
    "warnings": ["sparse id range: ~12% fill"],
    "recommended_profile": "balanced"
  }
}

Security note: resolved_plan embeds the full source connection config including credentials. Treat plan files with the same care as your rivet config file.


rivet apply

Execute a sealed plan artifact, or run a config’s exports wave-by-wave. The mode is chosen by the path’s extension:

  • .json → a sealed PlanArtifact: deserialize, validate staleness + cursor integrity, then execute the single export using the artifact’s pre-computed chunk boundaries — no SELECT min/max queries against the source.
  • .yaml / .yml → a config: run every export wave by wave in ascending wave: order (the wave each export was assigned by rivet plan). See Wave-ordered execution below.
rivet apply <PLAN_FILE | CONFIG> [OPTIONS]
Argument/FlagTypeDescription
PLAN_FILE / CONFIGstringPath to a plan JSON artifact, or a YAML config for wave-ordered execution (required)
--forceboolOverrides whichever safety gate refuses the run (ADR-0013). JSON-artifact mode: bypasses the staleness check of a full, chunked or keyset plan (> 24 h), extracts such a plan again after it already applied, and bypasses the incremental cursor-drift check (each logged and recorded in the run’s apply_context). YAML config mode: meaningful only with --resume, where it goes past the refusal to resume into a destination whose _SUCCESS marker is already present (the export then continues an interrupted run, else runs as a plain run does, beside the complete one); without --resume it is a warned no-op
--summary-outputPATHWrite the run summary as JSON to this file (the document rivet run --summary-output writes); a replay of a sealed plan that already applied writes the answering run’s entry (see below)
--jsonboolPrint the run summary as JSON to stdout at the end of the run

Staleness rules

A full, chunked or keyset plan is a point-in-time snapshot and ages; an incremental plan (mode: incremental, keyset_incremental) is a reusable description with no age limit (ADR-0005).

Plan age (full, chunked, keyset)Behavior
< 1 hourProceeds silently
1–24 hoursWarns and proceeds
> 24 hoursRejects — use --force to override

Cursor drift (Incremental exports)

Every apply of an incremental plan reads the cursor from state and delivers what arrived since; a cursor moved by an earlier apply of the same plan is its own progress. If another rivet run moved the cursor after the plan was generated, rivet apply rejects the artifact (RIVET_PLAN_ARTIFACT_STALE): the plan was reviewed for a window another run took. Regenerate with rivet plan.

Replay of an applied plan

A full, chunked or keyset plan that already applied to exit 0 is not run again: a replay of the same artifact (a scheduler retry) exits 0, prints apply: plan <plan_id> of export '<export>' was already applied by run <run_id> (...) on stderr and writes nothing. --force extracts it again.

The answer is machine-readable through the same channels a run uses: --summary-output and --json carry the run that applied the plan, read from its export_metrics row, with status: "already_applied" and answered_by_run naming it (per_export[].answered_by_run is null on every other entry; the aggregate counts the entry as skipped). No run, manifest or metrics row is written for the replay.

{
  "run_aggregate_id": "agg_20261017T130551070",
  "started_at": "2026-10-17T13:05:51.070310+00:00",
  "finished_at": "2026-10-17T13:05:51.072442+00:00",
  "duration_ms": 2,
  "config_path": "/srv/rivet/rivet.yaml",
  "parallel_mode": "sequential",
  "total_exports": 1,
  "success_count": 0,
  "failed_count": 0,
  "skipped_count": 1,
  "total_rows": 30,
  "total_files": 1,
  "total_bytes": 1225,
  "total_bytes_read": 1420,
  "per_export": [
    {
      "export_name": "pa_audit",
      "status": "already_applied",
      "run_id": "pa_audit_20261017T130550.929_55700",
      "answered_by_run": "pa_audit_20261017T130550.929_55700",
      "rows": 30,
      "files": 1,
      "bytes": 1225,
      "bytes_read": 1420,
      "duration_ms": 120,
      "mode": "full",
      "error_message": null,
      "skip_reason": null
    }
  ]
}

A scheduler tells the answer from a run by status, never by the stderr line.

Examples

# Apply the plan
rivet apply plan.json

# Apply an old plan (override staleness check)
rivet apply plan.json --force

What apply does NOT do

  • Does not re-read the config file
  • Does not re-run preflight queries
  • Does not recompute chunk boundaries (uses pre-computed ranges from the artifact)
  • Does not read the clock, except once for {date}: the sealed plan keeps the placeholder as written and apply resolves it to its own UTC day, through the same resolver rivet run uses, so a plan made before midnight and applied after it writes under the apply day. A plan made with rivet plan --date holds that day already (ADR-0005, amendment 2026-10-17)
  • Does not enforce preflight verdict (diagnostics are advisory — see ADR-0005)

State location

rivet apply opens .rivet_state.db next to the config file recorded inside the plan artifact (artifact.config_path), so apply shares the same state (cursors, manifests, schema history) as rivet run. It falls back to the plan file’s own directory — with a warning — only when the recorded config directory no longer exists or the artifact was generated before 0.7.5. Plan files do not need to sit beside the config.

Wave-ordered execution (YAML config)

rivet apply <config>.yaml runs every export in the config wave by wave, lowest wave: first, with a barrier between waves — every export in wave 1 finishes before wave 2 starts. Exports with no wave: run last. rivet plan --annotate-waves writes the wave: and parallel_safe: fields onto each export (you can hand-edit them; apply respects your order). Plain rivet plan is read-only and leaves the config untouched.

Within-wave parallelism. With parallel_export_processes: true in the config (or rivet apply --parallel-export-processes), the cheap exports within a wave — those rivet plan marked parallel_safe: true (cost class Low, < ~100K rows) — run concurrently as separate processes. A heavier export already chunk-parallelizes its own ranges internally, so it runs alone in its wave; two large tables at once would multiply load on the source. Each child still self-throttles via the adaptive governor. Without the flag, every export runs sequentially. parallel_safe also respects the campaign’s isolate_on_source — a cheap export on a contended shared source still runs alone.

# plan assigns waves → you review/edit → apply executes them, lowest wave first
rivet plan  -c rivet.yaml                   # review the schedule (read-only)
rivet plan  -c rivet.yaml --annotate-waves  # write wave:/parallel_safe: into the config
rivet apply rivet.yaml

A failing export does not stop its wave-mates: failures are collected and the run exits non-zero with the most stop-worthy error (data-integrity > internal > refusal > schema-drift > retryable > generic).

Resuming after a partial failure. Re-run with rivet apply <config>.yaml --resume: exports a prior run already completed (their destination carries a _SUCCESS marker and their last journaled run did not fail) are skipped, and an incomplete chunked export continues from its checkpoint — so recovering a run that failed mid-way does not redo the tables that already succeeded. Without --resume, a re-run re-exports everything.

partition_by exports are not expanded in this path yet — use rivet run for those.


rivet validate

Re-run manifest-aware verification against an existing destination — no extraction.

rivet validate --config <PATH> [OPTIONS]

The same M5/M6 checks rivet run --validate performs at end-of-run, exposed as a standalone command for between-run polling and triage. Reads manifest.json + _SUCCESS at the destination and head-checks every committed part for presence and recorded size_bytes. The source is not queried (use rivet reconcile for that). See ADR-0013 §“Subcommand carveouts” and ADR-0012 M5/M6.

By default validate resolves the destination prefix the same way run does ({date} becomes today’s UTC date). Use --date, --run-id, or --prefix to point at a prior run instead.

FlagShortTypeDescription
--config-cstringPath to YAML config file (required)
--export-estringValidate only a specific export by name
--formatpretty|jsonOutput format: pretty (human summary) or json (machine-readable)
--depthlight|sample|fullVerification depth: light (manifest + _SUCCESS), sample (+ part reconcile + untracked surplus), full (+ value-checksum re-read of every part; default). CSV parts carry no value checksum: at full each CSV part’s rows are re-counted against the manifest and a RIVET_VERIFY_VALUE_CHECK_NOT_AVAILABLE warning says no cell values were re-read
--output-oPATHWrite the JSON report to this file (only with --format json)
--dateYYYY-MM-DDResolve {date} to this date instead of today (UTC)
--run-idstringSubstitute {run_id} in the destination prefix template (composes with --date). No run lookup is performed — if the template has no {run_id} placeholder this has no effect; use --prefix for an arbitrary path
--prefixstringPoint at an explicit destination prefix

Exits non-zero when the manifest references a part that is missing or whose size does not match, and when the manifest records its last run as anything but success (RIVET_VERIFY_RUN_NOT_SUCCESSFUL, exit 1: a failed, interrupted or still-running export is not a completed dataset). A run that failed before its first write did not touch the prefix, so validate still reads the export that was complete before it. A legacy prefix (no manifest) falls back to the M6 reduced-guarantee path and is labelled legacy_run: true. A bucket or container that does not exist is not a legacy prefix: the export’s verdict carries RIVET_VERIFY_MANIFEST_READ_ERROR naming the bucket, and validate exits 1.

Examples

# Verify today's run at the configured destination
rivet validate -c my_export.yaml

# Verify a prior run by id, JSON report to a file
rivet validate -c my_export.yaml --run-id orders_20260521T120000 --format json -o verdict.json   # -o is ignored unless --format json is set

rivet reconcile

Partition/window reconciliation — re-runs per-chunk COUNT(*) on the source and compares with the stored per-chunk row counts from the last run. Surfaces matches, mismatches, and repair candidates without re-exporting data (Epic F).

rivet reconcile --config <PATH> --export <NAME> [OPTIONS]
FlagShortTypeDescription
--config-cstringPath to YAML config file (required)
--export-estringExport name to reconcile (required)
--formatpretty | jsonOutput format (default pretty)
--output-ostringWrite JSON report to this file (use with --format json)
--param-pKEY=VALUEQuery parameter (repeatable)

Scope (v1)

  • Chunked exports — supported. Requires a previous run with chunk_checkpoint: true so per-chunk ranges and row counts are persisted in .rivet_state.db.
  • Time-window — returns an error (“use chunked with chunk_by_days” for partition reconcile).
  • Snapshot / Incremental — no natural partitions; use rivet run --reconcile for a whole-export count check.

What it does

For each completed chunk task from the latest chunk run:

  1. Rebuilds the exact chunk query the pipeline used (same WHERE predicate, same dense/range shape — build_chunk_query_sql).
  2. Runs SELECT COUNT(*) FROM (<chunk_query>) AS _rc.
  3. Compares the source count with the stored rows_written for that chunk.

Each partition is classified as:

  • match — source and exported counts are equal.
  • mismatch — counts differ; partition is a repair candidate (note includes diff).
  • unknown — one of the counts is unavailable (chunk never completed, unparseable chunk keys); also a repair candidate.

Examples

# Human-readable summary
rivet reconcile -c my_export.yaml -e orders

# JSON report to file
rivet reconcile -c my_export.yaml -e orders --format json -o reconcile.json

Reports never re-export on their own — they surface what needs repair. They are not merely advisory though: a detected mismatch exits non-zero with the data-integrity class (exit 3), so CI can gate on it.

Verification strategy tradeoffs

Rivet has three verification mechanisms at different cost/precision tradeoffs:

MechanismWhat it checksCostWhen to use
rivet run --reconcileCOUNT(*) source vs exported rows for the whole export1 extra querySnapshot / incremental exports; cheap sanity check after every run
rivet reconcilePer-chunk COUNT(*) source vs stored chunk row counts1 query per chunkChunked exports with chunk_checkpoint: true; catches partial writes in individual chunks
rivet check --type-reportColumn type fidelity + warehouse compatibility1 LIMIT-0 probeBefore first export of a new table; after source schema changes

Rule of thumb:

  • Use --reconcile always for snapshot/incremental exports — cost is negligible.
  • Use rivet reconcile for chunked exports if data correctness is critical or the source is volatile.
  • Use rivet repair only when rivet reconcile surfaces mismatches — it re-exports only the flagged chunks.

rivet repair

Targeted repair of chunks flagged by reconcile. Prints a RepairPlan by default; with --execute, re-exports only the flagged chunk ranges (Epic H, ADR-0009 RR1–RR8).

rivet repair --config <PATH> --export <NAME> [OPTIONS]
FlagShortTypeDescription
--config-cstringPath to YAML config file (required)
--export-estringExport name to repair (must be mode: chunked) (required)
--reportpathPath to a reconcile JSON report (from rivet reconcile --format json). Omit to run reconcile in-process against the latest chunk run
--executeboolActually re-export the flagged chunk ranges. Without this flag, the plan is printed and nothing is executed (RR2)
--formatpretty | jsonOutput format for the plan / post-execute report (default pretty)
--output-ostringWrite plan / report JSON to this file (with --format json)
--param-pKEY=VALUEQuery parameter (repeatable)

Examples

# Dry run from the latest reconcile — prints the plan, nothing executes
rivet repair -c my_export.yaml -e orders

# Dry run from a saved reconcile report
rivet repair -c my_export.yaml -e orders --report reconcile.json

# Execute — re-runs only the flagged chunks
rivet repair -c my_export.yaml -e orders --report reconcile.json --execute

What --execute does and does not do

  • Re-runs only the flagged chunk ranges via ChunkSource::Precomputed — same SQL shape as extraction and reconcile (RR3).
  • Writes new files alongside originals named <export>_<ts>_chunk<idx>_<nonce>.<ext>, where <nonce> is a random 16-hex-digit value (RR5) — the nonce, not the second-granularity timestamp, is what guarantees a repair landing in the same second as the original can never clobber it. Rivet does not delete or overwrite prior files. Downstream deduplication (or a versioned output prefix) is the operator’s responsibility.
  • Leaves last_committed_* untouched (RR4) — repair is corrective, not commitment. last_verified_* re-advances only if a subsequent clean rivet reconcile runs.

rivet check

Preflight analysis: diagnose source health, estimate row counts, check indexes, recommend tuning. With --type-report, also introspects column types and validates them against a target warehouse.

The Strategy: and Mode: lines (and strategy / mode under --json) describe the plan rivet run builds for the export, from the same planner: the chunk column it resolved from the primary key, and full-scan for a mode: chunked export whose table fits one chunk, which runs as one unchunked pass. On MongoDB, check fails on a collection that does not exist (RIVET_SOURCE_COLLECTION_NOT_FOUND, exit 1), as run does.

rivet check --config <PATH> [OPTIONS]
FlagShortTypeDescription
--config-cstringPath to YAML config file (required)
--export-estringCheck only a specific export
--param-pKEY=VALUEQuery parameter (repeatable)
--type-reportboolRun a type fidelity report: show each column’s source type, Rivet type, Arrow type, and fidelity
--strictboolExit non-zero if any column mapping is lossy or unsupported (use with --type-report)
--jsonboolEmit type report as newline-delimited JSON instead of a table
--targetstringValidate types against a warehouse target: bigquery | snowflake | duckdb | clickhouse

Examples

# Standard preflight check
rivet check -c my_export.yaml

# Type fidelity report (human-readable table)
rivet check -c my_export.yaml --type-report

# Type report with BigQuery compatibility column
rivet check -c my_export.yaml --type-report --target bigquery

# Type report as JSON — pipe-friendly, one object per export
rivet check -c my_export.yaml --type-report --json

# Strict mode — exits 1 if any lossy or unsupported mapping exists
rivet check -c my_export.yaml --type-report --strict

Type report output

Export: orders  [target: bigquery]

  Column        Source type        Rivet type       Arrow type            Fidelity        Target type   Status
  ----------    ----------------   ---------------  --------------------  --------------  -----------   ------
  id            int4               int4             Int32                 exact           INT64         ok
  amount        numeric(15,4)      decimal(15,4)    Decimal128(15, 4)     exact           NUMERIC       ok
  created_at    timestamptz        timestamp_tz     Timestamp(us, UTC)    exact           TIMESTAMP     ok
  metadata      jsonb              json             Utf8                  logical_string  STRING        ok ~
  tags          text[]             list<text>       List(Utf8)            exact           REPEATED…     ok

Fidelity levels:

LevelMeaning
exactRound-trips without loss
compatibleStructurally compatible; minor representation difference
logical_stringSerialized to STRING/text (no native Arrow type)
lossyPrecision or range reduction
unsupportedNo safe mapping exists; the export fails with N column(s) have no safe Rivet mapping — add column overrides in rivet.yaml, and rivet check --strict exits non-zero

Output includes: table existence, estimated row count, index analysis, tuning recommendation.

check also asks the state the config uses (RIVET_STATE_URL, or the SQLite file beside the config) whether it answers. When check logged any ERROR line, it does not print Looks good. and exits non-zero with RIVET_CLI_CHECK_REPORTED_ERRORS.


rivet doctor

Verify source, state and destination connectivity/auth before running exports. The State check opens the state the config uses without creating or migrating it, and names the backend.

rivet doctor --config <PATH>
FlagShortTypeDescription
--config-cstringPath to YAML config file (required)

Example

rivet doctor -c my_export.yaml

Output:

rivet doctor: verifying auth for config 'my_export.yaml'

[OK]  Config parsed successfully
[OK]  State (sqlite /etc/rivet/.rivet_state.db)
[OK]  Source auth (Postgres)
[OK]  Destination Local(./output)

All checks passed.

When tls: is omitted from source: and the host is loopback, nothing is printed (local dev is exempt). On a remote host the [WARN] source: TLS is not enforced… line appears and the source check fails (TLS required — refusing to connect to a remote (non-loopback) host without TLS): fix it with tls: { mode: verify-full }, or explicitly opt into remote plaintext with tls: { mode: disable } on an already-trusted network path — see reference/config.md § TLS.


rivet init

Generate a YAML config scaffold (or a machine-readable discovery artifact) by connecting to PostgreSQL, MySQL, SQL Server, or MongoDB and introspecting tables/collections (read-only). Does not run an export. YAML scaffolds include meta_columns (exported_at / row_hash on by default); scaffolds with heuristic mode: chunked also include chunk_checkpoint: true — see init.md.

rivet init (--source <URL> | --source-env <ENV_VAR> | --source-file <PATH>)
           [--table <NAME>] [--schema <NAME>] [-o <PATH>] [--discover]

Exactly one of --source, --source-env, --source-file must be provided (enforced by the argument group).

With -o and RIVET_STATE_URL set, init first asks that state whether it answers and writes no config when it does not: every run of the config opens that state, and the primary keys init records there are what rivet load keys a query: export by.

FlagShortTypeDescription
--sourcestringConnection URL: postgresql:// | mysql:// | sqlserver:// | mongodb://. Visible in shell history / ps — avoid in production
--source-envenv var nameName of an env var that holds the URL (e.g. DATABASE_URL). URL never hits the command line. Recommended.
--source-filepathPath to a file containing just the URL on one line. Credentials stay on disk
--tablestringSingle table; optionally schema-qualified (public.orders on PostgreSQL, dbo.orders on SQL Server). Omit to scaffold all tables/views in a Postgres/SQL Server schema or MySQL database
--schemastringPostgreSQL: schema to list (default public). MySQL: database name when the URL omits one; a --schema naming a different database than the URL’s is refused — put the database in the URL instead
--output-ostringWrite output to file (default: print to stdout)
--discoverboolEmit a machine-readable JSON discovery artifact instead of YAML — includes ranked cursor/chunk candidates, row estimates, on-disk sizes, and coalesce-fallback hints

Examples

# One table → one export block
rivet init --source-env DATABASE_URL --table orders -o rivet.yaml

# PostgreSQL: entire schema (default public)
rivet init --source-env DATABASE_URL --schema public -o all_public.yaml

# MySQL: entire database from URL path
rivet init --source-file /run/secrets/mysql_url -o all_mydb.yaml

# JSON discovery artifact — ranked cursor/chunk candidates per table
rivet init --source-env DATABASE_URL --schema public --discover -o discovery.json

Narrative guide, heuristics, and Docker Compose examples: init.md.


rivet metrics

Show export run history (duration, row count, file size, status).

rivet metrics --config <PATH> [OPTIONS]
FlagShortTypeDefaultDescription
--config-cstring—Config file (required)
--export-estringallFilter by export name
--last-linteger20Number of recent runs to show

Example

rivet metrics -c my_export.yaml --last 10
rivet metrics -c my_export.yaml -e orders_daily

rivet journal

Inspect the structured run journal for an export — per-run event log with status, file/row/byte summary, retries, quality issues, schema changes, and the first error line.

rivet journal --config <PATH> --export <NAME> [OPTIONS]
FlagShortTypeDefaultDescription
--config-cstring—Path to YAML config file (required)
--export-estring—Export name to inspect (required)
--last-linteger5Number of recent runs to show
--run-idstring—Show a single specific run by ID

Examples

# Last 5 runs for the orders export
rivet journal -c my_export.yaml -e orders

# Last 10 runs
rivet journal -c my_export.yaml -e orders --last 10

# Single run by ID
rivet journal -c my_export.yaml -e orders --run-id orders_20260513T120000.123

Output

Each run is shown as a block:

✓ orders  success  12.3s
  run_id: orders_20260513T120000.123
  files:  3  rows: 150000  size: 4.2 MB

✓ = succeeded · ✗ = failed · • = partial / unknown. Retries, quality issues, schema changes, and first-line error text are appended when present.

Journal entries are persisted to .rivet_state.db (SQLite, migration v7) at the end of every run. An empty result means the export has not run yet in this state DB, or --run-id does not match any stored run.


rivet state

Manage export state (cursors, file manifests, chunk checkpoints).

rivet state show

Show current cursor state for all incremental exports.

rivet state show --config <PATH>

rivet state reset

Reset the cursor for a specific export (next run will re-export all rows).

rivet state reset --config <PATH> --export <NAME>

The reset clears the progress the export holds on the source this config names (its cursor and any interrupted keyset run), so the config’s source URL must resolve. An export of the same name that reads another source through the same state database keeps its cursor.

While a checkpointed run of that export (chunk_checkpoint: true) is alive in another rivet process, the reset is refused with RIVET_STATE_RUN_IN_PROGRESS (exit 2, retryable) and removes nothing: wait for the run, or stop its process, and repeat the command. A run that was killed does not hold the reset back.

rivet state accept

Keep the stored progress of an export whose query or source was edited on purpose.

rivet state accept --config <PATH> --export <NAME> [--param KEY=VALUE ...]

A run refuses stored progress that was written reading another table, another schema or other rows (RIVET_STATE_CURSOR_STREAM_MISMATCH). state accept is the answer “the edit is meant, go on from where the export was”: it records the stored progress of that one export as belonging to what the config reads now, and leaves the cursor value, its column and an interrupted run’s anchor as they are. The next run continues from the cursor under the new query, so rows of the new query below the cursor are not delivered; rivet state reset is the command that delivers them.

state accept also answers RIVET_STATE_CURSOR_SOURCE_RESPELLED: when the export holds no progress on the source the config names, and holds a cursor on another source that delivers to the same destination, it moves that cursor (with its checkpoint rows) onto the source the config names, and the next run continues from it. With several such sources it takes the one that ran last. The move is recorded as a run_journal row with status accepted. To start the export over instead, follow it with rivet state reset.

  • It acts on the one export named, on the source this config names, once. A later edit is refused again until it is accepted too; there is no setting that accepts edits in advance.
  • It reads the config as rivet run does, so pass the same --param values.
  • A cursor written for another cursor_column or keyset key is not accepted: the command stops with RIVET_STATE_CURSOR_OWNER_MISMATCH (exit 5) and changes nothing.
  • When no run would be refused (no stored progress, or it already belongs to what the export reads) the command says so and changes nothing. The same holds for a cdc export, which stores a log position and no cursor.
  • While a run of that export is alive in another rivet process the command is refused with RIVET_STATE_RUN_IN_PROGRESS (exit 2, retryable).
  • Each acceptance that changed the row is recorded in the run journal: rivet journal --config <PATH> --export <NAME> lists it with status accepted and a line naming what the progress was written reading and what it is kept for.

rivet state files

List files produced by exports.

rivet state files --config <PATH> [--export <NAME>] [--last <N>]
FlagShortDefaultDescription
--export-eallFilter by export name
--last-l50Number of recent files

rivet state chunks

Show chunk checkpoint status for a chunked export.

rivet state chunks --config <PATH> --export <NAME>

rivet state reset-chunks

Clear persisted chunk checkpoint rows (chunk_run / chunk_task) so the next chunked run starts a fresh plan.

One export — same as targeting a single table name:

rivet state reset-chunks --config <PATH> --export <NAME>

Every “stuck” export in this config — resets checkpoints only when chunk_run.status is still 'in_progress' (process killed mid-run, concurrent worker left state behind, etc.). Exports whose chunk run already finished normally (completed) are skipped. Names that appear in state but were removed from the YAML are skipped with a printed note.

rivet state reset-chunks --config <PATH> --stuck-checkpoints

Alias (same semantics — checkpoint stuck, not “last metric row failed”):

rivet state reset-chunks --config <PATH> --failed

Then run rivet run --config <PATH> --resume (or a normal run without --resume) as needed.

A run that is still alive is not stuck. --export <NAME> against one is refused with RIVET_STATE_RUN_IN_PROGRESS (exit 2, retryable), naming the run; --stuck-checkpoints leaves that export alone, prints why, and clears the others. A run that was killed does not hold either form back.

rivet state vacuum

Shrink a SQLite state DB. Deleted rows and schema migrations leave free pages that SQLite reuses but never returns to the disk; VACUUM rewrites the file without them and prints the size before and after (a pilot state DB went from 46.8 MB to 18.8 MB after the v32 migration). It takes the database’s write lock while it runs, so concurrent runs wait for it (busy_timeout, 10 s). On a PostgreSQL state it does nothing: autovacuum reuses the space there.

rivet state vacuum --config <PATH>

rivet state progression

Show explicit committed and verified export boundaries (Epic G / ADR-0008).

rivet state progression --config <PATH> [--export <NAME>]
ColumnMeaning
COMM MODE / COMMITTEDStrategy (incremental / chunked) and boundary value (cursor string or chunk #N) durably committed to the destination
COMMITTED ATUTC timestamp of the committing run
VERI MODE / VERIFIEDSame shape, but only advanced by a full-match rivet reconcile (zero mismatches, zero unknowns)

The progression table is advisory: it does not gate rivet run, rivet apply, or rivet reconcile. Consumers are operators and external monitoring.


rivet completions

Generate shell completion scripts.

rivet completions <SHELL>
ShellCommand
Bashrivet completions bash > ~/.local/share/bash-completion/completions/rivet
Zshrivet completions zsh > ~/.zfunc/_rivet
Fishrivet completions fish > ~/.config/fish/completions/rivet.fish
PowerShellrivet completions powershell > _rivet.ps1
Elvishrivet completions elvish > ~/.config/elvish/lib/rivet.elv

rivet schema

Emit machine-readable schemas for Rivet’s data contracts.

rivet schema config

Today rivet schema config prints the JSON Schema for the rivet.yaml config to stdout. The schema is generated from the running binary’s Rust types, so it always matches the config grammar this version accepts. Pipe it to a file and reference it via a # yaml-language-server: $schema=… header so VS Code / Neovim’s YAML language server highlights invalid keys, suggests enum values, and surfaces required fields as you edit:

rivet schema config > rivet.schema.json
# then, at the top of rivet.yaml:
# yaml-language-server: $schema=./rivet.schema.json

State backend

By default Rivet keeps all run state (cursors, metrics, manifests, chunk checkpoints, schema drift, run journal, progression) in a SQLite file — .rivet_state.db — placed next to the config file. This works for local and single-node deployments.

For stateless containers / Kubernetes where the rivet pod is ephemeral or replicated, set RIVET_STATE_URL to a PostgreSQL connection string:

export RIVET_STATE_URL=postgresql://rivet:rivet@localhost:5433/rivet_state
rivet run --config rivet.yaml

Rivet creates all state tables automatically on first connect, running the full migration ladder up to the current schema version (the same schema-version sequence as SQLite). No manual DDL required.

A RIVET_STATE_URL that is set and is not a postgres:// or postgresql:// URL (a typo such as postgre://, another engine’s URL) is refused by every command that opens the state (RIVET_STATE_URL_SCHEME_UNSUPPORTED, exit 1): it is never replaced by the SQLite file. An unset or empty variable selects SQLite.

A run starts by recording itself in the state. If the state does not take that write (a read-only file, directory or filesystem, a role without write access), the run is refused before it exports anything (RIVET_STATE_NOT_WRITABLE). SQLite writes -wal and -shm files beside .rivet_state.db, so the directory must be writable as well as the file: on a read-only directory no command can open the state, and each is refused with the same code. If the incremental cursor cannot be stored after the rows and the manifest are written, the run exits non-zero (RIVET_STATE_CURSOR_NOT_STORED): the rows are delivered, and the next run delivers them again from the previous cursor.

Docker Compose (local dev)

docker-compose.yaml includes a dedicated postgres-state service on port 5433 (separate from the source postgres service on port 5432 so data and state never mix):

docker compose up -d postgres-state
export RIVET_STATE_URL=postgresql://rivet:rivet@localhost:5433/rivet_state
rivet run --config pilot.yaml

Security

  • Passwords are redacted from all log and error messages: postgresql://user:***@host/db.
  • A state URL for a non-loopback host must say how to connect. ?sslmode=require (or verify-ca / verify-full) negotiates TLS and needs TLS enabled on the state server; ?sslmode=disable connects in plaintext, the operator’s choice for a private network, with one WARN line. A remote URL with neither (or with prefer / allow, which rivet cannot negotiate) is refused RIVET_STATE_URL_TLS_REQUIRED, exit 5, before any connection:
export RIVET_STATE_URL="postgresql://rivet:secret@db.internal/rivet_state?sslmode=require"
# a private network without TLS on the state server:
export RIVET_STATE_URL="postgresql://rivet:secret@10.0.0.5/rivet_state?sslmode=disable"
  • The RIVET_STATE_URL value is not embedded in plan artifacts or config files. It is resolved from the environment at runtime.

Environment variables

VariableDescription
RUST_LOGLog level: error, warn, info, debug, trace
DATABASE_URLCommonly used with url_env: DATABASE_URL in source config
RIVET_STATE_URLPostgreSQL URL (postgres:// or postgresql://) for the state backend. When set, activates the PG backend instead of the default SQLite file; any other non-empty value is refused. Example: postgresql://rivet:rivet@localhost:5433/rivet_state

Example: verbose logging

RUST_LOG=debug rivet run -c my_export.yaml

Example: PostgreSQL state backend

export RIVET_STATE_URL=postgresql://rivet:rivet@localhost:5433/rivet_state
RUST_LOG=info rivet run -c my_export.yaml

Exit codes

CodeMeaning
0All exports succeeded
1Usage / config error — config parsing/validation, a bad command, or an export error no other class claims. Fix the input; retrying won’t help
2Retryable transient failure (connection loss, timeout, throttling), or busy: another live rivet process holds the lease or lock the command needs (RIVET_STATE_RUN_IN_PROGRESS, RIVET_LOAD_TABLE_BUSY, RIVET_STATE_LEASE_LOST), or a BigQuery failure Google documents as retryable (RIVET_LOAD_WAREHOUSE_UNAVAILABLE) — safe to retry; the retry succeeds once that process ends. Clap argument-parse errors also exit 2 (distinguishable by the usage text and absence of an Error: line)
3Data-integrity failure (quality gate / reconcile / validate / duplicate-guard) — stop and investigate
4Schema drift (on_schema_drift: fail tripped)
5Protective refusal — rivet stopped on purpose so as not to lose, duplicate or overwrite data (a foreign checkpoint, a newer state DB, a cursor-owner mismatch, …). Retrying unchanged refuses again; a human decides
6Internal — an invariant rivet relies on did not hold; a bug, please report it

Every coded error and the exit its kind maps to: errors.md.