Skip to content

CLI Reference

Installing the barca package (pip install barca or uv add barca) puts the barca command in the environment’s bin/. The output on this page was captured from a terminal running the current release, with long paths shortened. barca <command> --help and barca docs describe the version you have installed.

Use barca --help to discover commands by purpose, barca <command> --help for concise usage, and barca <command> --examples for the complete runnable example collection. For detailed behavior, load one topic with barca docs <topic>; barca docs --json lists available topics for scripts and agents. Help and examples never load a project or run work.

The target comes first, then the files:

Terminal window
barca get <asset> [files] # assets: served from cache when nothing changed
barca run <task> [files] # tasks: the task always runs
  • get is for assets and sensors, run is for tasks. Both serve unchanged upstream assets from cache.

  • Using the wrong one exits 2 and names the other, for example 'validate' is a task — use `barca run` instead.

  • A file before the target exits 2 and prints the corrected command:

    $ barca run pipeline.py notify
    error: the target comes before the files
    barca run notify pipeline.py

barca list, barca status, --dry-run and barca sql show what barca sees without running anything.

The CLI contract lists every command, flag, environment variable, exit code and JSON output schema, marked stable or experimental, with the policy for changing them.

barca get [target[,target...]] [file.py|dir/ ...] [--refresh a,b [--no-cascade] | --refresh-all]
Get assets — cache-aware; prints their artifact paths
barca run <task[,task...]> [file.py|dir/ ...] [--refresh a,b [--no-cascade] | --refresh-all] Run task(s) (always re-run)
barca plan [file.py|dir/ ...] Emit the execution plan as JSON (experimental)
barca doctor [file.py|dir/ ...] [--json] [--server [URL] | --local]
Diagnose project prerequisites (and a server) without running steps
barca logs [run_id] [--step NAME] [--stream S] [-f] [--json] A run's recorded step stdout and stderr (default: the most recent run)
barca history [RUN_ID] [--status S] [--target NAME] [--triggered-by T] [--since WHEN] [-l N | --all] [--json|--pretty]
Show recent runs, or one run's steps and errors
barca stats <target> [file.py|dir/ ...] Show timing/cache stats for an asset
barca gc [--dry-run] [--max-size SIZE] [--json] Evict cached results to keep .barca/ under its size limit (experimental)
barca serve [file.py|dir/ ...] [--port N] [--host IP] [--watch] [--no-schedule] [--timezone TZ] [--read-only] [--drain-timeout SECONDS] [--max-runs N] [--capacity N] [--run-timeout SECONDS]
Run the HTTP API server
barca queue [pause|resume] [--server [URL]] [--json] A server's run queue; pause or resume it (experimental)
barca list [file.py|dir/ ...] [-l N | --all] [--json] List discovered definitions and their deps
barca status [target[,target...]] [file.py|dir/ ...] [--json] [--sample N] [--problems] [--state S,S]
Cache state, last run and artifact shape per node
barca sql "<query>" [file.py|dir/ ...] [--json] [-l N | --all]
Query cached results with DuckDB (experimental)
(install: uv add 'barca[sql]')
barca state backups [--json] List the metadata backups, local and in the store
barca state restore [--from N|NAME|PATH] Put a metadata backup back (the current DB is kept aside)
barca state reset [--yes] Start the metadata DB over (the old one is moved aside)
barca docs [topic] [--all] [--json] Built-in manual
barca version Print version
barca --help Show help

get, run, status, list, history and stats also take --server [<url>], which runs the command on a barca serve and prints it here as the local command would, and --local, which ignores a configured server (experimental; Server mode, barca docs server-mode). sql takes both too: a server resolves its views, and the query always runs here. doctor takes both too: --server checks that server beside this machine (a configured one is checked without it), --local checks this machine only.

barca pipeline.py is read as barca get pipeline.py. It is kept for compatibility; write barca get.

Files are optional on every command. Without them barca reads every .py file under the project root (the nearest directory holding barca.toml, else the current one) that imports barca. Files or directories narrow it; a directory given first needs a trailing / or must be .. Node ids are relative to the root. See Discovery.

get, run, list, status, sql, history and stats choose what to print on stdout by one rule, first match wins:

  1. A flag: --json forces JSON; --pretty forces human output (tables, summaries). get and run also keep -o json|pretty, older spellings that cannot be combined with --json / --pretty. get -o path|value choose what get returns (see get); run -o value prints only the task’s value.
  2. BARCA_OUTPUT=json or BARCA_OUTPUT=pretty in the environment. Any other value is a usage error (exit code 2).
  3. The terminal: stdout is a TTY → human output; a pipe, file or subprocess → JSON.
Terminal window
barca list pipeline.py # a table in your terminal
barca list pipeline.py | cat # JSON: {"nodes": [...], "total", "truncated", "root"}
barca list pipeline.py --json # JSON even in a terminal
barca history --pretty # a table even when piped
BARCA_OUTPUT=json barca get summary pipeline.py

get and run print their JSON as one line. list, status, sql, stats, history and plan print one indented JSON document over several lines, so parse all of stdout, not its last line.

plan always prints JSON and docs always prints markdown (docs --json for JSON). Progress and errors always go to stderr; the progress bar (the only ANSI output) draws only when stderr is a terminal, and --agent replaces it with plain progress lines.

A library warning that a step’s process repeats is printed once per run and then counted ([barca] 79 more: ...); see barca docs agents, “Repeated warnings”.

plan, get, run and --dry-run also report plan-time warnings: one [barca] warning: ... line each on stderr, and a warnings array in their JSON output ([] when there are none). The one warning so far is unused_input: a step declares an input its function never uses, which is still loaded and still part of the cache key. Warnings do not change the exit code (barca docs assets, “Unused inputs”). A query receiver shadowed by a match capture or nested function/class definition is treated conservatively, so dynamic SQL access does not cause a false unused-input warning.

Compute one or more assets and print where their results are. Only the target and what is upstream of it are considered, and a step whose code and inputs are unchanged is served from cache.

$ barca get summary pipeline.py --json
[barca] 2/2 steps | done in 0.0s
{"elapsed_seconds":0.08844638,"final_output":{"_barca_artifact":{"format":"json","path":".barca/artifacts/pipeline.py--summary/2d10....json","size_bytes":24}},"phases":1,"run_id":"14f3f967c1b6","status":"success","steps":[{"attempts":1,"detail":"no cached result for this code and these inputs","elapsed_seconds":0.0023,"id":"pipeline.py:raw_data","kind":"asset","reason":"not_materialized","run_hash":"a8e7...","status":"ran"},{"attempts":1,"detail":"no cached result for this code and these inputs","elapsed_seconds":0.0007,"id":"pipeline.py:summary","kind":"asset","reason":"not_materialized","run_hash":"2d10...","status":"ran"}],"steps_executed":2,"warnings":[]}

The first line is progress, on stderr. The JSON is one line on stdout (hashes shortened here). final_output is a pointer to the artifact, {"_barca_artifact": {"path", "format", "size_bytes"}}, whatever its format. Run it again and every step is "status":"cached", with its artifact path, and "steps_executed":0. In a terminal the same run prints a summary and the path, with a small json value (up to 10 lines) previewed under it:

$ barca get summary pipeline.py
Run 14f389a807a2 | got 'summary' in 0.050s (0 steps, 1 phase)
.barca/artifacts/pipeline.py--summary/2d10....json
{
"count": 3,
"total": 6
}

To read the result:

Terminal window
barca get summary pipeline.py -o value # only the value, pretty-printed
barca get summary pipeline.py -o value --json # the JSON result with the value in final_output
barca get summary pipeline.py -o path | xargs cat # only the path, one line per artifact
barca get summary pipeline.py -o url --expires 12h # a signed HTTPS link to the result in the store

-o value reads json, parquet, CSV and pickle with the reader barca.get() uses; a table prints as a list of row objects. -o path is the default spelled out: with --json it prints the default JSON. Up to 0.24 final_output was the value of a json result and a terminal printed the value; -o value prints it now. -o url (experimental) prints a signed HTTPS link to each result in a cloud store, valid for --expires (default 1h, at most 7d); with --json each _barca_artifact pointer gains url and url_expires_at. See Remote storage: signed URLs.

Partitioned targets. A partitioned asset returns every key, sorted: final_output is an object keyed by partition key ("region=emea"; a compound key is "day=d1,region=emea"), each a pointer, or a value under -o value, and -o path prints one line per key. --partition region=emea,amer selects keys across the whole run: every partitioned node with that dimension, upstream of the target and downstream, computes only those keys, they are returned, and --refresh refreshes only them. A JSON object selects on several dimensions, --partition '{"region": "emea", "day": ["d1", "d2"]}'. A selected key has the same run hash as in a full run, so a later full run serves it from cache. A collect() fan-in over a narrowed node (and what is below it) is not run: status skipped, reason partition_selection, one note on stderr, and the exit code is unchanged. With no target the selection applies to every asset and sensor; barca run and barca plan take it too. JSON gains partition_selection (the selection, each narrowed node’s selected and skipped key counts, skipped_nodes). A dimension no node in the cone has, a value no node with that dimension has, or a target the selection would skip is a usage error (exit 2). Up to 0.29.2 --partition narrowed only the target. See Parametrized Assets and Partitions. When some keys fail the run fails (exit 1, failed_node names the key) and final_output in the result line holds the keys that succeeded; how a partial failure is reported may still change (barca docs partitions).

A run lists at most 1000 keys (0.30.2). When a partitioned target returns more and no --partition selected them, final_output lists the keys this run computed and counts the ones it served from cache, so a tick that recomputed one key of 200k returns one path:

{"_barca_partitions": {"keys": 200000, "computed": 1, "cached": 199999, "truncated": false,
"outputs": {"retailer=r000042": {"_barca_artifact": {"path": "...", "format": "json", "size_bytes": 49}}},
"hint": "..."}}

outputs holds pointers, values under -o value or links under -o url, for at most 1000 computed keys (truncated when a cold run computed more). -o path and the table print those paths and a note on stderr. --partition returns every key it selects, however many, and barca sql reads every current key’s rows. barca.get() raises for such a target; pass partition=. The same object is the outcome barca serve records for the run.

With no target (barca get pipeline.py, or barca get), barca gets every asset and sensor and returns the last asset’s artifact. Tasks are skipped, and stderr names them and the barca run command. A sensor nothing depends on still runs. A file with only tasks gets nothing: exit 0, "steps": [], and a stderr note pointing at barca run.

Every get/run usage error exits 2. An unknown or ambiguous target (and run without one) ends with Run `barca list <files>` to see available assets and tasks.; the others state their own fix: the corrected command for the wrong order, barca run for a task given to get, the valid names for an unknown --refresh name. A name close to one barca knows (a target, a --refresh name, a flag or a subcommand) is preceded by Did you mean '...'?: the best match within one edit (two for a name longer than four characters), or two that tie. The exit code is still 2 and nothing runs.

Flag Meaning
--refresh a,b Recompute the named assets (one comma-separated list) and everything downstream of them in the target’s cone. The target itself may be named.
--no-cascade With --refresh: recompute only the named assets. Downstream assets stay cached and do not reflect the refresh; barca warns.
--refresh-all Recompute every asset in the target’s cone.
--dry-run Report each step as cached or to run, and why, without running or writing anything.
--json, --pretty Force JSON or human output.
-o path Print only the artifact paths, one per line (one per partition key). With --json, the default JSON.
-o value Print only the value. With --json, the JSON result with the value in final_output.
-o json|pretty Older spellings of --json / --pretty.
--partition KEY=V[,V] Select partition keys across the cone (a JSON object for compound keys): every partitioned node with that dimension computes only them, and fan-ins over them are skipped. Works without a target.
--agent Plain progress lines on stderr in place of the progress bar.
--fields a,b Keep only these keys on each entry of steps: id, kind, action, status, reason, detail, changed, run_hash, artifact, warning, partitions, env.
--env <name> Use a named environment’s cache and history.

--no-cache was removed in 0.24.0; use --refresh-all.

A dry run of the same target:

$ barca get total pipeline.py --dry-run
Dry run: barca get total (nothing executed, nothing written)
STATUS WHY STEP
will run no cached result for this code and these inputs pipeline.py:orders
will run no cached result for this code and these inputs pipeline.py:total
2 will run, 0 cached, 0 unknown

An asset that reads a @sensor is cached against the sensor’s value: when the sensor returns a new value, the asset and everything downstream of it re-run. --dry-run predicts from the sensor’s last recorded value and says so in detail; before the sensor has ever run, its consumers are unknown (reason: "sensor_output_unknown").

An asset that reads an @asset(cutoff=True) is cached against the bytes that asset wrote, not its run hash: when it reruns and writes the same bytes, what reads it stays cached. A dry run cannot know those bytes before it runs, so its consumers are unknown (reason: "cutoff_pending") until it has; see Early cutoff.

Each entry in the result’s steps array says what happened to that step. A node that declares environment variables with env=[...] also carries env, the values the step was hashed with (null when unset, <redacted> for names like *_TOKEN), and its --agent progress line ends with env NAME=value .... See Decorators.

Run a task and what it depends on. The task always runs; it is never cached. The assets upstream of it are served from cache when unchanged, as with barca get. run takes the same flags as get. In a terminal it prints the task’s value under the summary; -o value prints only the value.

$ barca run notify pipeline.py
[barca] 1/3 steps | done in 0.0s
Run 14f1e316840c | ran 'notify' in 0.074s (1 step, 1 phase)
Value:
{
"sent": 80.5
}
Terminal window
barca run deploy pipeline.py --refresh fetch,transform # recompute these and everything downstream of them
barca run deploy pipeline.py --dry-run --refresh fetch # preview the cascade

To have assets pick up outside data that changed, put a @sensor in front of them (see Decorators).

barca run and barca get take one comma-separated list of targets (no spaces):

Terminal window
barca run validate_registry,validate_names pipeline.py # both tasks, one run
barca get summary,orders pipeline.py --dry-run # preview the union
  • The union of the targets’ cones is planned once, so a shared upstream step runs (or is served from cache) once.
  • Every name is checked before anything runs; an unknown name, a task passed to get, or an empty name (a,,b) is a usage error (exit 2).
  • Every target runs even if another fails. A failure skips only the steps that depend on it ("status": "skipped", reason upstream_failed), and the exit code is 1 if any target failed.
  • With several targets, final_output is replaced by targets, keyed by target name in the order given:
{"status": "success", "run_id": "...", "elapsed_seconds": 0.2, "steps_executed": 3, "phases": 2, "steps": [...],
"targets": {"validate_registry": {"status": "success", "final_output": {"models": 2}},
"validate_names": {"status": "success", "final_output": {"lowercase": true}}}}

A failed target is {"status": "failed", "failed_node": "pipeline.py:...", "error": "..."}. --dry-run with several targets reports targets in place of target, each with a summary counted over that target’s cone. The full shapes are in barca docs agents.

The document includes plan_version: 1, independent of the package and database versions. Consumers of saved plans should reject unsupported versions.

Parse the source files and print the execution plan as JSON, without running anything. The pool size matches execution: a positive BARCA_POOL_SIZE, else [execution] pool_size, else the available cores, or the capacity when it is smaller; pool_size and pool_source say which. Experimental: the layout follows the planner and may change between releases. Each phase’s reason is {"type": "initial"} or {"type": "fan_in", "node_id": "..."}. Planning reads no state, so plan takes no --env and does not say what is cached; use --dry-run or barca status for that. Its only option is --help.

$ barca plan pipeline.py
{
"plan_version": 1,
"total_steps": 3,
"phases": [
{
"reason": {
"type": "initial"
},
"streams": [
{
"stream_id": "p0-w0",
"steps": [
"pipeline.py:orders",
"pipeline.py:total",
"pipeline.py:notify"
]
}
]
}
],
"warnings": []
}

Run JSON includes steps[].elapsed_seconds and attempts for executed steps, including failures; cache hits and dry runs omit them. Elapsed time sums active worker time across attempts, including import/input loading/call/serialization and excluding retry backoff. After a worker disconnect, the coordinator’s observed in-flight time is used. For partitioned steps it sums executed keys, while attempts is the maximum for one key.

Under --agent (and in every run barca serve starts) a worker prints [barca] importing <module> before loading a module; otherwise it prints nothing. Imports longer than one second add import_seconds, included in elapsed time and summed across attempts/keys. Reused modules add no import time. Agent completion and permanent failure lines append attempts=N for retries. history --json sums recorded materialization attempts (including existing upload-failure retry counts); stats retains each materialization’s attempts and final-attempt duration. Timeout errors name the step id.

After verifying that the process or container has stopped, explicitly reconcile an uncertain row with barca history --reconcile RUN_ID --json. This records only that row as interrupted and prints it in the normal history envelope (runs, total: 1, truncated: false); --fields and human output also work. It refuses a proven live owner, a terminal row, an unknown id, and combining --reconcile with --limit or --all. It preserves recorded step counts, successful materializations and artifacts, and leaves unknown end time and duration as null. It does not stop a process. Ordinary history reads still keep an uncertain owner running; reconciliation is your assertion that it stopped. The change is local to the selected environment’s metadata database.

Show recent runs from .barca/metadata.db: run id, command, target, status, step counts and timing. TARGET is the run’s job by function name, all for a run without targets, cut at 24 characters with …. The human table defaults to the 10 most recent; JSON returns every run unless --limit is explicit.

$ barca history
RUN_ID CMD TARGET STATUS STEPS CACHED TIME STARTED
------------------------------------------------------------------------------------------------------
14f1e316840c run notify success 1 2 0.1s 2026-10-21 02:39:25 UTC
14f01f296a75 get country_report,weekly_s… success 0 2 0.0s 2026-10-21 02:39:25 UTC
14f3870e173f get all success 2 0 0.1s 2026-10-21 02:39:25 UTC

Times are UTC. --fields takes run_id, command, files, target, job, status, steps_total, steps_executed, steps_cached, attempts, started_at, finished_at, elapsed_seconds, triggered_by, scheduled_at, schedule_id, client_run_id, failure. triggered_by says what started the run: manual (a command typed on a machine), api (an HTTP request to barca serve), schedule (a cron tick, with the tick in scheduled_at, RFC 3339 in UTC, and the scheduled node in schedule_id) or client; it is null for runs recorded before barca recorded it. job is the run’s job in full: all, or its resolved node ids sorted and comma-separated (null for runs recorded before 0.29.0); target is the target as typed.

Terminal window
barca history --fields run_id,triggered_by,scheduled_at # what started each run
barca history -l 25 # last 25; human default 10, JSON default every run
barca history --json # {"runs": [...], "total": N, "truncated": bool, "hint"?: "..."}
barca history --fields run_id,status # JSON with only these keys per run

History JSON returns every run unless --limit N is explicit; the human table defaults to 10. When more runs exist than are shown, the JSON has "truncated": true, the total, and a hint; the table prints the same hint as one line on stderr. See Bounded output. Each run’s files is an array of the .py files it was given.

Terminal status and successful/failed step rows commit together. Required database-write failures report an infrastructure error and preserve previous progress/history and run state, which normally remains unfinished. Captured output is written separately after that transaction.

Mid-run database writes commit completed-step rows and their progress count together. Failed batches are retained and retried without waiting for another step; replay does not count a completed step twice. Progress can be absent while the database cannot commit. The final write still records completed results not saved during execution.

Filters combine with AND, and --limit, total and truncated count the runs they keep: --status (comma-separated: success, failed, cancelled, error, running, interrupted), --target NAME (the run’s target or job is that node; runs of every node are left out), --triggered-by (comma-separated: manual, api, schedule, client) and --since (30m, 24h, 7d, 2w, an RFC 3339 time or a UTC date). A failed run’s row ends with its first failed step and the first line of its error; JSON runs carry it as failure: {node, error} and as the flat failed_node and error (null otherwise), so --fields run_id,failed_node,error needs no nesting.

Terminal window
barca history --status failed --since 24h
barca history --target clean --triggered-by schedule --json
barca history 836fdca186c8 # one run in detail
barca history 836fdca186c8 --json # {run, steps: [...], hint?}

barca history RUN_ID prints the run’s fields, the steps it recorded (status, attempts, time, peak memory, run hash, artifact path, format and size), and for each failed step its error, traceback, when the worker died how it ended (worker exit: SIGKILL (possibly the OOM killer)), and the last 20 lines it printed (output_tail in JSON, without the traceback). Each step’s JSON has max_rss_bytes, the worker process’s peak resident memory while the step ran (on Linux the step’s own; elsewhere only when the step raised the worker’s peak), and cpu_seconds. Bytes DuckDB spilled to disk are not recorded: DuckDB keeps no total of them. A hint on stderr names barca logs RUN_ID --step STEP --stream stderr. Cached steps are counted in steps_cached, not listed: a run records no row for them. With a server configured, both forms answer from the server; a run only this machine has is an error whose remediation is barca history RUN_ID --local.

$ barca history 836fdca186c8
run 836fdca186c8 get clean failed 0.1s 1 attempt
started 2026-10-22 22:57:27.000 UTC finished 2026-10-22 22:57:27.000 UTC
triggered by api
files pipeline.py
steps: 1 executed (1 failed), 1 cached (cached steps are not listed)
STEP STATUS ATTEMPTS TIME RUN_HASH ARTIFACT
pipeline.py:clean failed 1 - d64e1b00647e -
pipeline.py:clean failed: ZeroDivisionError: division by zero
Traceback (most recent call last):
File "/srv/pipelines/pipeline.py", line 11, in clean
return [{"id": o["id"], "per_unit": 10 / o["qty"]} for o in orders]
~~~^~~~~~~~~~
ZeroDivisionError: division by zero
hint: barca logs 836fdca186c8 --step clean --stream stderr

A run’s status is running, success, failed, cancelled or interrupted. A run records each step as it finishes, so a running run already counts them in steps_executed, and barca status from another terminal shows them as cached. A run whose process was killed is interrupted (no finished_at); the next barca get reuses the steps it had recorded. A run killed in a container is interrupted too, once a container starts again on the same .barca volume (from 0.20.0; earlier versions left it running). For runs started with 0.20.0 or later, a run stays running whenever barca cannot establish that its process is gone. Earlier runs retain their legacy process-id and host-name checks. See barca docs cache, “While a run is going, and after one is killed”, and the shared-state limitations.

Evict cached results so .barca/ stays under its size limit ([cache].max_size or BARCA_CACHE_MAX_SIZE, default 10% of the filesystem; see Cache size). barca already does this after every get and run once the limit is passed; barca gc does it now. Experimental.

Terminal window
barca gc --dry-run # what would be evicted, per tier, and what it leaves
barca gc # evict now, down to 80% of the limit
barca gc --max-size 5GB # a one-off tighter budget
barca gc --max-size off # only leftovers and superseded results, all of them
barca gc --json # {size_bytes, limit_bytes, target_bytes, after_bytes, tiers, evicted, failed, restricted, dry_run}
$ barca gc --dry-run --max-size 400KB
would evict 4 files (1.4 MB), leaving 342 KB of 400 KB:
superseded 3 files 807 KB
least recently read 1 file 549 KB

Each tier that has something to evict gets a line; leftovers and held by the store appear the same way. Under the limit it prints one line, such as nothing to evict: 284 KB of 3.4 GB.

The order is leftovers, superseded results, local copies the artifact store holds, then the least recently read current results. --dry-run lists exactly what barca gc then deletes. While another run is in progress on this .barca/, only leftovers and superseded results are considered (restricted says why). --env NAME evicts only that environment’s results. A file that cannot be deleted is skipped with a warning; exit 3 only when a metadata database cannot be read. The remote store is never touched.

Show statistics for one node: how many times it executed, timings (average, median, p95, max), how many executions repeated unchanged inputs, and recent runs. The target is required. Times are UTC.

$ barca stats total pipeline.py
Asset: pipeline.py:total
Total materializations: 1
Timing: avg 0.001s median 0.001s p95 0.001s max 0.001s
Re-executed with unchanged inputs: 0.0%
Recent runs:
ELAPSED STATUS ATTEMPTS CREATED
0.001s success 1 2026-10-21 02:39:25 UTC

A cache hit executes nothing, so it is not counted here; barca history counts cache hits per run. In the JSON the re-execution share is cache_hit_rate (the key keeps its name).

The JSON has id, total_runs, cache_hit_rate, avg_elapsed_seconds, median_elapsed_seconds, p95_elapsed_seconds, max_elapsed_seconds, max_rss_bytes and median_max_rss_bytes (peak memory over the recorded executions) and recent_runs (each elapsed_seconds, status, created_at, error_message, attempts, run_id, cpu_seconds, max_rss_bytes). run_id names the run, whose every step barca history RUN_ID lists.

Terminal window
barca stats summary pipeline.py --json # one JSON object; the node id is `id`
barca stats summary pipeline.py --fields status,error_message # JSON; trims recent_runs entries
barca stats summary --fields run_id,elapsed_seconds,max_rss_bytes # each recent run's id, time and peak memory

Start an HTTP server with a JSON API, the cron scheduler and the web UI at /ui/. It binds to 127.0.0.1 by default; --host 0.0.0.0 listens on every interface. It has no authentication. See Server API for the endpoints and Deploying for running it behind nginx.

Terminal window
barca serve pipeline.py # default port 8274
barca serve pipeline.py --host 0.0.0.0 # every interface (containers, VMs); no auth
barca serve --timezone utc # every file in the project; cron evaluated in UTC
barca serve pipeline.py --drain-timeout 120 # on SIGTERM, runs in flight get 2 minutes
barca serve pipeline.py --max-runs 2 # at most 2 runs at once; the others wait in the queue
barca serve pipeline.py --capacity 4 # the weight= of every run's running steps adds up to at most 4
barca serve pipeline.py --run-timeout 3600 # stop a run that takes longer than an hour
Flag Default Meaning
-p, --port <PORT> 8274 Port to listen on.
--host <IP> 127.0.0.1 IP address to bind on; 0.0.0.0 (or ::) listens on every interface.
--watch off Re-parse the DAG when a source file changes. Files added later still need a restart.
--no-schedule off Do not fire Schedule(...) nodes.
--timezone <TZ> local Timezone for cron: local, utc or an IANA name such as America/New_York. Any other value is a usage error (exit 2).
--read-only off Refuse runs, never schedule, read the metadata DB from copies.
--drain-timeout <SECONDS> [server] drain_timeout, else 30 On SIGTERM or SIGINT, how long runs in flight get to finish before they are put back in the run queue for the next start. 0 requeues them at once.
--max-runs <N> [server] max_runs, else one per core How many runs execute at once; the others wait in the run queue, in order (experimental).
--capacity <N> [server] capacity, else none The most the weight= of the running steps of every run may add up to, admitted first come, first served across runs; GET /queue shows it (experimental).
--run-timeout <SECONDS> [server] run_timeout_seconds, else none Stop a run that takes longer, recorded as an error naming the setting. 0 is no limit; Schedule(..., timeout_seconds=N) wins for its ticks.
--env <name> default Use a named environment’s cache and history.

Every accepted run is recorded in the run queue before the server answers, and a restart resumes the queued runs in order under their run ids. SIGTERM or SIGINT drains the server: no queued run starts (new ones are still accepted and queued), the scheduler stops, /health reports draining, and runs in flight finish, uploads included. The exit code is 0 when they all finished, and 0 when --drain-timeout passed and the runs left were put back at the head of the queue (they start again from the beginning after the restart). A second signal cancels them instead (recorded as cancelled, reason server shutdown): exit code 1. See Server API and Restarting without losing work.

A barca server’s run queue: the runs executing and the runs waiting to start, in order. It needs a server (--server URL, BARCA_SERVER or [server] url); without one it exits 2. Experimental.

Terminal window
barca queue --server # running and queued runs (a table in a terminal)
barca queue --server --json # {paused, paused_at, draining, max_runs, running, queued}
barca queue pause --server # queued runs wait; runs in flight go on; kept across restarts
barca queue resume --server # queued runs start again, in order

pause and resume answer with the queue after the change, and exit 2 on a --read-only server. See barca docs scheduling, “The run queue”.

With a remote store, barca serve backs its metadata DB up every minute it changed (not with --read-only), and barca state restore refuses to run while it is up. See Remote storage.

List every asset, task and sensor barca finds, with its kind, freshness and inputs. A scheduled node also shows its cron and its next fire time, to the second. The time is the next match of the cron expression in the local time of the machine list runs on (the table column is NEXT FIRE (LOCAL TIME)). list talks to no server, so it does not know a server’s --timezone: a server started with one fires at the same wall-clock time in that zone, and its GET /schedule reports the times it will fire at.

$ barca list pipeline.py
NAME KIND FRESHNESS DEPS
------------------------------------------
pipeline.py:orders asset always -
pipeline.py:total asset always pipeline.py:orders
pipeline.py:notify task always pipeline.py:total

The FRESHNESS column shows what the decorator declares. Only schedule has an effect, and only under barca serve.

Terminal window
barca list # every node in the project
barca list pipelines/ # only files under pipelines/
barca list pipeline.py --json # {"nodes": [{id, kind, freshness, schedule?, inputs, env, next_fire?}], "total", "truncated", "root"}
barca list pipeline.py --fields id,inputs # JSON with only these keys per node

In JSON, freshness is always, manual or schedule (lowercase, like kind); a scheduled node also has schedule, its cron expression. list reads no state, so it takes no --env.

list prints every node in JSON by default, in topological order; human tables show at most 100; see Bounded output.

When any node declares environment variables (@asset(env=["SOURCE_CSV"])), the table gains an ENV column listing them; --json always includes env (an empty list when none are declared).

List-shaped commands (list, status, history) return every item in JSON unless --limit N is explicit, including piped output and --fields. Human tables show at most 100 nodes or 10 history runs by default; --all removes the bound. Their JSON keeps its envelope:

{"nodes": [...], "total": 312, "truncated": true,
"hint": "pass --limit N for more, or --all for all 312 nodes"}

truncated and total are always present, hint only when truncated. --limit N and --all choose how many items to print.

--fields a,b keeps only those keys on each item of any JSON output: nodes (list, status), runs (history), recent_runs (stats), steps (get/run, including --dry-run) and topics (docs). It implies JSON everywhere; combining it with --pretty, -o pretty or run -o value is a usage error (exit 2). An unknown key is a usage error (exit 2) that lists the valid ones. Limits bound how many items are printed, never their content: error messages and tracebacks are always complete. plan has no per-item objects (its steps are id strings), so it takes no --fields.

Show, for every node in scope, its kind, inputs, whether it is partitioned, its cache state with the reason, its last run, and the shape of its result. It runs nothing.

Terminal window
barca status total pipeline.py # only `total` and its upstream cone
barca status total,notify pipeline.py # several targets: the union of their cones
barca status pipeline.py --json # {target, targets, nodes, summary, total, truncated, root}
barca status pipeline.py --json --sample 5 # plus up to 5 sample rows per json/parquet/CSV artifact
barca status --problems # only nodes that need attention
barca status --state stale,never_run # only these cache states

--problems keeps every node that is not fine: every state but cached and always_runs, a task or sensor whose last run failed, and a schedule that is paused or failing (max_failures reached). --state keeps the named states. The summary still counts every node, and the JSON names the filter in filter. A node whose last run failed shows the first line of its error in WHY (failed: ZeroDivisionError: division by zero); the whole message is last_materialization.error. A scheduled node has schedule (cron, next_fire, failures, max_failures, failing, paused, paused_until, last_status, live) and the table a SCHEDULE column: from a server it is the server’s scheduler’s view, the numbers of GET /schedule and the web UI; on this machine it is what the metadata DB holds, with live: false. See Agent Skill and barca docs agents, “Debugging pipelines”.

NAME KIND STATE WHY LAST RUN SHAPE DEPS
orders asset cached materialized success 2026-10-21 02:39:25 UTC 0.00s 3 rows x 2 cols -
total asset cached materialized success 2026-10-21 02:39:25 UTC 0.00s dict (1 key) orders
notify task always-runs task success 2026-10-21 02:39:25 UTC 0.00s dict (1 key) total
2 cached, 0 stale, 0 never run, 0 partial, 0 unknown, 1 always run

Cache state (cache.state) is the decision --dry-run makes, from the same code path. JSON spells it in snake_case, exactly like the summary keys; the table prints never-run and always-runs:

state meaning reasons
cached a successful result matches this code and these inputs materialized
unsynced cached and computed here, but its upload to the artifact store failed after the step succeeded; the next get or run uploads it (remote storage) local_only (table: local only)
stale ran before, but would run again changed, upstream_stale, failed, artifact_missing, evicted
never_run no successful materialization recorded no_record, failed
partial partitioned, some keys cached partitions_missing
unknown dynamic partitions whose source has not run partitions_unknown
always_runs tasks and sensors task, sensor

changed means the run hash differs from the last successful materialization, and cache.changed says what differs: code, decorator argument NAME, decorator NAME, helper FILE:NAME (a function or constant the code uses, in its own file or a project module), input ID, sensor ID, env NAME or kind. detail and the table’s WHY column name the first and count the rest (changed: helper pipelines/common.py:bucket_ltv (+1 more)), and a dry run’s step carries the same changed list. A result recorded before 0.29.0 has no record of the parts, and its node stays a plain changed. artifact_missing means the result is recorded but its artifact file is gone and a run would have to read it, so it would be computed again; a missing artifact that nothing reads leaves the node cached (barca docs cache, “A cached result whose artifact is missing”).

Last materialization is the most recent execution recorded in the metadata DB, success or failure. Cache hits do not change it. For a partitioned node it is the latest run’s first key in sort order, so identical runs report the same key.

Shape is read from the artifact file only, by a small Python helper; your code is never imported. Parquet gives rows and columns from the file footer (needs pyarrow), json gives type and, for a list or object, rows, columns or keys, and pickle gives type only. Remote artifacts are read from the store; a failure is reported in shape.note and the command still exits 0. Details: barca docs status.

Partitioned assets appear as one node with partitions: {total, cached, missing, missing_keys} (up to 20 keys). last_materialization is the most recently run key (named in its partition field), and shape describes that key’s artifact.

Each node also lists env, the environment variables it declares with env=[...]. Like list, status returns every node in JSON unless --limit N is explicit. Its human table shows at most 100 nodes unless you pass --limit N or --all; the summary still counts every node, and the JSON reports total and truncated (see Bounded output).

With an artifact store, a cached unpartitioned node is cached only when this machine’s copy, the store’s object and the recorded digest agree; otherwise it is unsynced with cache.reason local_only, store_only or mismatch, and barca get repairs it without running a step. Status reads the metadata DB as it is at that moment: while a barca get is running, the steps it has finished already show as cached (a partitioned asset as partial).

Status executes no pipeline step and records no new run; partitions(<expression>) may evaluate Python while resolving keys. An unknown target is a usage error (exit 2). See barca docs status.

Query cached results with DuckDB. Every asset, sensor and task with a result on disk is a view named after its declared name=, or its function when unset; a partitioned asset is one view with a partition column. No step runs, your code is not imported, and no run is recorded. Only parquet, CSV and json results are views; a pickled result cannot be queried. Install SQL support with uv add 'barca[sql]'. With a remote store the artifacts of the views a query names are downloaded into .barca/sql-cache/ and reused. barca’s own records are tables in the barca schema: barca.runs, barca.materializations and barca.nodes. With a server configured, the views and those tables come from the server’s ledger and the query runs on your machine; the server never runs SQL. Experimental.

$ barca sql "select region, sum(amount) as amount from orders group by 1 order by 1"
region amount
amer 70.5
emea 10.0
Flag Default Meaning
-l, --limit <N> 100 Maximum number of rows returned.
--all off Return every row. Cannot be combined with --limit.
--json, --pretty by terminal Force JSON or the table.
--env <name> default Query a named environment’s results.
--server [<url>] the configured server Resolve the views from that server’s ledger; the query runs here.
--local off Query this machine’s own results and records, ignoring a configured server.

See barca sql for views, errors and limits.

With a remote store ([remote] uri or BARCA_REMOTE_URI), get and run back the metadata DB up on a one-minute checkpoint and when they end, and serve every minute it changed: to .barca/backups/<name>.db and <uri>/<env>/state/backups/<name>.db, five copies kept in each, each with a <name>.json sidecar (run_id, rows, digest, barca_version, db_format, and from 0.29 host and project: the machine and project directory that took it). Nothing is downloaded before a run. A store is one machine’s backup: when another machine’s backup is newer than this one’s own, a backup or barca state backups warns once, pruning keeps each machine’s newest backup, and restore without --from picks this machine’s.

Terminal window
barca state backups # newest first: # NAME WHERE SIZE ROWS RUN HOST
barca state backups --json # {backups: [{index, name, where, size_bytes, run_id, rows, digest, agree, barca_version, db_format, host, project, mine}], local_dir, store}
barca state restore # put this machine's newest backup back (the newest, if it has none)
barca state restore --from 1 # the newest, whichever machine took it
barca state restore --from 2 # by index; or --from 2026-10-14T18-02-11.482Z-3f9a1c2e
barca state reset --yes # move .barca/metadata.db aside to metadata.db.reset-<UTC>, start empty
barca state restore --from .barca/metadata.db.reset-2026-10-20T18-02-11.482Z # undo a reset

restore checks the copy first (a whole SQLite file, integrity, barca’s tables, a usable format), the local copy before the store’s, then moves the current database to .barca/metadata.db.pre-restore and puts the copy in place; one line on stderr says what it restored and from where, and --json adds {restored, from, source, pre_restore, rows, run_id, host, project} on stdout. It never runs by itself. Exit 2: no backup, an unknown --from, or a copy that fails the check (nothing is changed). Exit 3: barca serve is running on the environment. See Remote storage.

reset (experimental) needs no store: it moves the database and its -wal and -shm aside to .barca/metadata.db.reset-<UTC> in the same directory and creates an empty one, for a database that cannot be opened or migrated or a history you want to start over. Nothing is deleted and artifacts are left alone. It asks first in a terminal; --yes skips the question and is required when stdin is not a terminal (exit 2 otherwise). --json prints {database, moved_to, moved, undo}. Exit 3: a run is in progress on this machine, or barca serve is running on the environment (use its UI’s Reset database button or POST /state/reset). Undo with barca state restore --from <the moved file>, which also takes any database file’s path.

The manual, compiled into the binary, so it works offline and describes the installed version. Every command’s --help ends with examples.

Terminal window
barca docs # topic index with one-line summaries
barca docs types # one topic as markdown (output formats, annotations, duckdb)
barca docs examples/duckdb # a runnable example pipeline
barca docs skill # the agent skill (SKILL.md), with its frontmatter
barca docs --all # every topic in one stream
barca docs --json # topic index as JSON; add a topic for its full text
barca docs --fields name # JSON index with only topic names (name, summary, content)
barca docs cache --pretty # markdown, the default even when piped

barca docs skill is the short agent skill an AI agent loads once. barca docs contract is the CLI contract. barca docs agents describes the output contract for scripts and AI agents.

Code kind Meaning
0 success
1 step_failed a step of yours raised; the traceback is included and the run is recorded as failed
2 usage bad flags or arguments, unknown target, get on a task or run on an asset, unreadable or invalid .py file, invalid --env or barca.toml
3 infra barca or its environment failed: metadata DB, worker pool, remote state, I/O
130 cancelled stopped by Ctrl-C (SIGINT) or SIGTERM

A closed stdout or stderr never makes barca panic, never stops a run and is not an error: output for the closed stream is dropped, a get or run finishes and is recorded, and the exit code is the one the command would have had with a reader (barca list | head -1 exits 0). See the CLI contract.

An error in a pipeline file fails every command that reads the file with exit 2, whatever the target: a syntax error, or a decorator called with an argument it does not define (@asset(after=other), input= for inputs=). See Accepted arguments. A syntax error in a helper module a pipeline file imports fails, with exit 2 before anything runs, every command that plans a step of that file (plan, status, get, run, --dry-run), naming the module, line, column and source line; list and steps of other files are not affected.

In JSON output mode (whenever results are JSON: piped or captured stdout, --json, -o json or BARCA_OUTPUT=json; plan always; docs with --json), an error is a single JSON line, the last JSON error object on stderr (a failed run adds its summary afterwards):

{"code":2,"error":"Asset 'nope' not found. Available: pipeline.py:orders, pipeline.py:total, pipeline.py:notify","kind":"usage","remediation":"Run `barca list pipeline.py` to see available assets and tasks."}

error, code, kind and remediation are always present. When a step fails (kind: "step_failed") the envelope also has node (the failing step’s id), traceback (the Python traceback of your code, with barca’s own frames removed), artifact_dir (where that step’s artifacts are stored; a local path or remote URI, which may not exist if the step never succeeded) and phase (where the step failed: user_code for your code; load_inputs, split_output, write_output or sink for barca’s own work around it, whose traceback keeps barca’s frames and whose prose says failed in barca; null when unknown, such as a worker that died):

{"artifact_dir":".barca/artifacts/pipeline.py--clean","code":1,"error":"step 'pipeline.py:clean' failed: ZeroDivisionError: division by zero","kind":"step_failed","node":"pipeline.py:clean","phase":"user_code","remediation":"Fix the error in 'pipeline.py:clean' (see the traceback) and re-run the same command. Steps that succeeded are cached and will not re-run.","traceback":" File \"/home/you/project/pipeline.py\", line 11, in clean\n return x / 0\n ~~^~~"}

In human mode (a terminal, --pretty, -o pretty, -o path or -o value) the error is prose, with the remediation on the last lines. Errors never go to stdout. The Python API raises barca.BarcaError with the same fields as attributes (kind, code, remediation, node, traceback, artifact_dir, phase).

When a step fails in JSON mode, get/run still print one result line on stdout, so you can read the outcome without parsing stderr: {"status": "failed", "failed_node": ..., "error": ..., "run_id", "steps", ...}, where the failed step’s status is failed. A successful result has "status": "success". After the error, stderr ends with one greppable line: [barca] run failed: step 'pipeline.py:clean' failed: ZeroDivisionError: division by zero; 0 downstream steps skipped (exit 1).

A worker process that dies while it runs a step (the kernel’s out-of-memory killer, a segfault in a C extension, os._exit() in your code) fails the step with exit 1, and the error names how the process ended, in the result JSON, the envelope and the run failed: line:

worker for pipeline.py:train exited with SIGKILL (possibly the OOM killer)
worker for pipeline.py:train exited with SIGSEGV
worker for pipeline.py:train exited with code 3

SIGKILL is what the out-of-memory killer sends: check the container’s or host’s memory limit. Several heavy steps running at once in separate workers add up; give them a weight= so fewer run at once (see Decorators). A worker frozen waiting for its parallel() branches adds while it waited for its parallel() branches. When the exit status is not available within a few hundred milliseconds, the error is the plain worker disconnected.

$ barca version
barca 0.27.0

barca --version prints the same. Neither takes --json.

get, run, status, sql, history, stats, serve, state backups, state restore and state reset accept --env <name> (default: BARCA_ENV, then default_env in barca.toml, then default). plan and list read no state and reject it. Each environment has its own cache, artifacts, history and remote location. get, run and serve print the environment and artifact store once at startup on stderr when default_env or an artifact store is in effect. See Configuration.

  • get and run print a human summary in a terminal; they used to print JSON there. Piped or captured stdout is still JSON.
  • list --json and history --json print an envelope (.nodes, .runs); up to 0.9.0 they printed a bare array.
  • barca run serves upstream assets from cache; it used to recompute all of them. Pass --refresh-all for the old behavior. --burst a,b is now --refresh a,b.
  • --refresh cascades downstream; it used to recompute only the named assets. Pass --no-cascade for the old behavior. A step re-run by the cascade reports reason: "refresh_cascade".
  • A sensor’s value is part of its consumers’ run hashes; it used not to be. Assets that read a sensor re-ran once after that upgrade.
  • From 0.20.0: SIGTERM stops get, run and serve the way Ctrl-C does. get and run exit 130 (cancelled) and record the run as cancelled; before, SIGTERM killed barca at once (a shell reported 143) and the run was later reported as interrupted. As process 1 of a container barca used to ignore SIGTERM.
  • From 0.20.0: barca serve --timezone with a value barca does not know exits 2; it used to print a warning and use local time.
  • From 0.20.0: POST /run/{target} and POST /get/{target} answer 404, 409 or 400 for a target that cannot run; they used to answer 200 with a run_id of a run that then failed.
  • From 0.20.0: the barca list table column NEXT FIRE is NEXT FIRE (LOCAL TIME).
  • From 0.25.0: get prints artifact paths by default. final_output in the JSON is always a pointer, and a terminal shows the path with a small json value previewed under it; -o value prints the value. A partitioned target returns every key, and --partition selects keys. (From 0.30.2, past 1000 keys only the keys a run computed are listed.) .barca/ has a size limit ([cache] max_size, default 10% of the filesystem) and is evicted past it.
  • From 0.26.0: a remote store fails soft once the steps are done: an upload that fails after every step succeeded exits 0 with a warning (it was 3), and the result is unsynced until the next command uploads it.
  • From 0.26.0: the shared history (optimistic mode) is gone. [remote] state, state_uri, push_retries, BARCA_STATE, BARCA_STATE_URI and BARCA_PUSH_RETRIES are usage errors; nothing is pulled before a run, and the store holds backups instead (Remote storage). barca serve works with a store.
  • From 0.27.0: barca serve drains on SIGTERM or SIGINT: runs in flight finish, for up to --drain-timeout seconds, before it exits; it used to cancel them at once (serve).
  • From 0.29.4: a draining barca serve accepts new runs into its run queue (202, pending) instead of refusing them with 503, and at --drain-timeout it puts the runs left back in the queue (exit 0) instead of cancelling them (exit 1); only a second signal cancels them. --server clients and logs -f --server wait up to 60 seconds (BARCA_SERVER_RECONNECT) for a restarting server instead of exiting 3 at once (serve).

barca logs RUN_ID --pretty prints what a run’s steps wrote, without progress or prefixes: their stdout, and their Python stderr (logging, warnings, sys.stderr) with the traceback of each failed attempt. Writes straight to file descriptors 1 and 2 (native code, subprocesses) are not recorded. Without a run id, barca logs reads the most recent run (running or finished) and names it in a line on stderr: barca: logs of the most recent run: 07c4a64d0540 (run notify, success, started 2026-10-21 06:40:36 UTC). --json returns [{node_id, stream, ts, line}] in recorded order: stream is stdout or stderr, ts when the line was written (RFC 3339 UTC, milliseconds). --stream stderr keeps one stream, --timestamps puts the time before each text line. --step report selects a function name; use an exact node id if names collide, or a partitioned id to select one key. --env dev reads that environment’s history. It reads no pipeline source and contacts no store. Unknown runs/steps and ambiguous names exit 2; silent executed steps return no lines. Lines written before a failed step are retained. Recording stops after the first complete user line that reaches or exceeds 16 MiB of cumulative text; the crossing line and a marker are retained, and framework notes may follow. This is a soft threshold, not a hard byte or memory bound.

barca logs -f follows a run: it prints lines as they arrive (one JSON object per line with --json) and exits when the run ends, 0 when it succeeded, 130 when it was cancelled, 1 otherwise. Without a run id it follows the newest running run.

Terminal window
barca run report pipeline.py # in one terminal
barca logs -f --step report # in another
barca logs --step report --stream stderr --timestamps # after a failure: its logging and traceback

With a server configured (or --server), barca logs reads the server’s runs, scheduled ones included; --local reads this machine’s (barca docs logs, “Debugging a live pipeline”).

Task return values remain usable through barca run --json, -o value, and barca.run(...). During get/run, non-JSON modes print stdout lines to stderr with [node_id] prefixes; --agent framework lines keep [barca] prefixes. JSON output suppresses captured prints; retrieve them with barca logs using the result’s run id.

See Doctor and local debugging for prerequisite checks and a local-only debugging profile.