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 runrivet <command> --help).
Machine-readable output. Most commands emit JSON via a boolean
--jsonflag (run,check,doctor,metrics,state …);validate(and thereconcile/planreport writers) instead take--format json. This split is a known inconsistency to be unified in a future release — until then, pass--helpto confirm which idiom a given command uses.
Global
rivet [--json-errors] [COMMAND] [OPTIONS]
rivet --version # print version
rivet --help # show help
| Flag | Description |
|---|---|
--json-errors | Output 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]
| Flag | Short | Type | Description |
|---|---|---|---|
--config | -c | string | Path to YAML config file (required) |
--export | -e | string | Run only a specific export by name |
--validate | bool | Validate 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 | |
--reconcile | bool | Run COUNT(*) on source query and compare with exported rows | |
--resume | bool | Resume 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 | |
--force | bool | Override 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-exports | bool | Run 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-processes | bool | Run each export as a separate child process | |
--summary-output | PATH | Write run aggregate to this file as JSON | |
--json | bool | Print run aggregate to stdout as JSON after the run | |
--param | -p | KEY=VALUE | Query 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]
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--config | -c | string | — | Path to YAML config file (required) |
--export | -e | string | all | Plan only a specific export |
--param | -p | KEY=VALUE | — | Query parameter (repeatable) |
--output | -o | string | stdout | Write plan JSON to this file |
--format | pretty|json | pretty | pretty prints a human summary; json writes the full artifact | |
--date | YYYY-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:
| Line | Meaning |
|---|---|
Batch size | Rows fetched per query. adaptive if batch_size_memory_mb is set. |
Batch memory | Estimated range: narrow (~200 B/row) to wide (~10 KB/row) tables. |
RSS guard | Process-level RSS threshold. Fetching pauses if exceeded (0 = disabled). |
Throttle | Delay 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 planis 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:
| Factor | Effect on actual RSS |
|---|---|
| Highly variable TEXT/BLOB values | Actual batches can be 2–10× the wide estimate |
| Sparse nullable columns | Actual batches will be below the narrow estimate |
| Compression buffers in the Parquet writer | Adds 50–200 MB on top of the Arrow batch size |
| Tokio runtime, connection pool, jemalloc | Adds 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_planembeds 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 sealedPlanArtifact: deserialize, validate staleness + cursor integrity, then execute the single export using the artifact’s pre-computed chunk boundaries — noSELECT min/maxqueries against the source..yaml/.yml→ a config: run every export wave by wave in ascendingwave:order (the wave each export was assigned byrivet plan). See Wave-ordered execution below.
rivet apply <PLAN_FILE | CONFIG> [OPTIONS]
| Argument/Flag | Type | Description |
|---|---|---|
PLAN_FILE / CONFIG | string | Path to a plan JSON artifact, or a YAML config for wave-ordered execution (required) |
--force | bool | Overrides 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-output | PATH | Write 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) |
--json | bool | Print 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 hour | Proceeds silently |
| 1–24 hours | Warns and proceeds |
| > 24 hours | Rejects — 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 resolverrivet runuses, so a plan made before midnight and applied after it writes under the apply day. A plan made withrivet plan --dateholds 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.
| Flag | Short | Type | Description |
|---|---|---|---|
--config | -c | string | Path to YAML config file (required) |
--export | -e | string | Validate only a specific export by name |
--format | pretty|json | Output format: pretty (human summary) or json (machine-readable) | |
--depth | light|sample|full | Verification 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 | -o | PATH | Write the JSON report to this file (only with --format json) |
--date | YYYY-MM-DD | Resolve {date} to this date instead of today (UTC) | |
--run-id | string | Substitute {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 | |
--prefix | string | Point 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]
| Flag | Short | Type | Description |
|---|---|---|---|
--config | -c | string | Path to YAML config file (required) |
--export | -e | string | Export name to reconcile (required) |
--format | pretty | json | Output format (default pretty) | |
--output | -o | string | Write JSON report to this file (use with --format json) |
--param | -p | KEY=VALUE | Query parameter (repeatable) |
Scope (v1)
- Chunked exports — supported. Requires a previous run with
chunk_checkpoint: trueso 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 --reconcilefor a whole-export count check.
What it does
For each completed chunk task from the latest chunk run:
- Rebuilds the exact chunk query the pipeline used (same
WHEREpredicate, same dense/range shape —build_chunk_query_sql). - Runs
SELECT COUNT(*) FROM (<chunk_query>) AS _rc. - Compares the source count with the stored
rows_writtenfor that chunk.
Each partition is classified as:
match— source and exported counts are equal.mismatch— counts differ; partition is a repair candidate (note includesdiff).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:
| Mechanism | What it checks | Cost | When to use |
|---|---|---|---|
rivet run --reconcile | COUNT(*) source vs exported rows for the whole export | 1 extra query | Snapshot / incremental exports; cheap sanity check after every run |
rivet reconcile | Per-chunk COUNT(*) source vs stored chunk row counts | 1 query per chunk | Chunked exports with chunk_checkpoint: true; catches partial writes in individual chunks |
rivet check --type-report | Column type fidelity + warehouse compatibility | 1 LIMIT-0 probe | Before first export of a new table; after source schema changes |
Rule of thumb:
- Use
--reconcilealways for snapshot/incremental exports — cost is negligible. - Use
rivet reconcilefor chunked exports if data correctness is critical or the source is volatile. - Use
rivet repaironly whenrivet reconcilesurfaces 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]
| Flag | Short | Type | Description |
|---|---|---|---|
--config | -c | string | Path to YAML config file (required) |
--export | -e | string | Export name to repair (must be mode: chunked) (required) |
--report | path | Path to a reconcile JSON report (from rivet reconcile --format json). Omit to run reconcile in-process against the latest chunk run | |
--execute | bool | Actually re-export the flagged chunk ranges. Without this flag, the plan is printed and nothing is executed (RR2) | |
--format | pretty | json | Output format for the plan / post-execute report (default pretty) | |
--output | -o | string | Write plan / report JSON to this file (with --format json) |
--param | -p | KEY=VALUE | Query 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 cleanrivet reconcileruns.
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]
| Flag | Short | Type | Description |
|---|---|---|---|
--config | -c | string | Path to YAML config file (required) |
--export | -e | string | Check only a specific export |
--param | -p | KEY=VALUE | Query parameter (repeatable) |
--type-report | bool | Run a type fidelity report: show each column’s source type, Rivet type, Arrow type, and fidelity | |
--strict | bool | Exit non-zero if any column mapping is lossy or unsupported (use with --type-report) | |
--json | bool | Emit type report as newline-delimited JSON instead of a table | |
--target | string | Validate 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:
| Level | Meaning |
|---|---|
exact | Round-trips without loss |
compatible | Structurally compatible; minor representation difference |
logical_string | Serialized to STRING/text (no native Arrow type) |
lossy | Precision or range reduction |
unsupported | No 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>
| Flag | Short | Type | Description |
|---|---|---|---|
--config | -c | string | Path 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.
| Flag | Short | Type | Description |
|---|---|---|---|
--source | string | Connection URL: postgresql:// | mysql:// | sqlserver:// | mongodb://. Visible in shell history / ps — avoid in production | |
--source-env | env var name | Name of an env var that holds the URL (e.g. DATABASE_URL). URL never hits the command line. Recommended. | |
--source-file | path | Path to a file containing just the URL on one line. Credentials stay on disk | |
--table | string | Single 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 | |
--schema | string | PostgreSQL: 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 | -o | string | Write output to file (default: print to stdout) |
--discover | bool | Emit 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]
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--config | -c | string | — | Config file (required) |
--export | -e | string | all | Filter by export name |
--last | -l | integer | 20 | Number 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]
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--config | -c | string | — | Path to YAML config file (required) |
--export | -e | string | — | Export name to inspect (required) |
--last | -l | integer | 5 | Number of recent runs to show |
--run-id | string | — | 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 rundoes, so pass the same--paramvalues. - A cursor written for another
cursor_columnor keyset key is not accepted: the command stops withRIVET_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
cdcexport, 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 statusacceptedand 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>]
| Flag | Short | Default | Description |
|---|---|---|---|
--export | -e | all | Filter by export name |
--last | -l | 50 | Number 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>]
| Column | Meaning |
|---|---|
COMM MODE / COMMITTED | Strategy (incremental / chunked) and boundary value (cursor string or chunk #N) durably committed to the destination |
COMMITTED AT | UTC timestamp of the committing run |
VERI MODE / VERIFIED | Same 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>
| Shell | Command |
|---|---|
| Bash | rivet completions bash > ~/.local/share/bash-completion/completions/rivet |
| Zsh | rivet completions zsh > ~/.zfunc/_rivet |
| Fish | rivet completions fish > ~/.config/fish/completions/rivet.fish |
| PowerShell | rivet completions powershell > _rivet.ps1 |
| Elvish | rivet 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(orverify-ca/verify-full) negotiates TLS and needs TLS enabled on the state server;?sslmode=disableconnects in plaintext, the operator’s choice for a private network, with oneWARNline. A remote URL with neither (or withprefer/allow, which rivet cannot negotiate) is refusedRIVET_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_URLvalue is not embedded in plan artifacts or config files. It is resolved from the environment at runtime.
Environment variables
| Variable | Description |
|---|---|
RUST_LOG | Log level: error, warn, info, debug, trace |
DATABASE_URL | Commonly used with url_env: DATABASE_URL in source config |
RIVET_STATE_URL | PostgreSQL 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
| Code | Meaning |
|---|---|
| 0 | All exports succeeded |
| 1 | Usage / config error — config parsing/validation, a bad command, or an export error no other class claims. Fix the input; retrying won’t help |
| 2 | Retryable 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) |
| 3 | Data-integrity failure (quality gate / reconcile / validate / duplicate-guard) — stop and investigate |
| 4 | Schema drift (on_schema_drift: fail tripped) |
| 5 | Protective 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 |
| 6 | Internal — 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.