Reference

Command line

vero has five commands. plan loads a plan and reports on it, run loads and runs it, report renders an HTML page from a saved event log, import writes a plan from a Postman collection, a Hurl file or a curl command, and version prints the build. Every flag belongs to one invocation: nothing on the command line is read from the plan, and nothing in a plan changes a flag.

Command Does
vero plan [flags] plan.yaml loads the plan; prints what it would send, its graph, or one task explained
vero run [flags] plan.yaml loads and runs the plan; exits by the worst task state
vero report --html out.html events.ndjson renders the HTML report from an event log after the fact
vero import postman|hurl|curl ... writes a plan on stdout (importing)
vero version prints the module version, the Go toolchain and the commit

Usage errors

A command line vero cannot act on exits 64 before anything loads. A bare vero prints the usage to stderr and exits 64; vero --help and vero help run print to stdout and exit 0. An unknown command, an unknown flag or the wrong number of arguments prints the error and where to look:

$ vero run --frobnicate plan.yaml
vero run: unknown flag: --frobnicate
run 'vero run --help' for usage

Flags may come before or after the plan file. --jobs and --repeat below 1 are refused with vero run: --jobs and --repeat must be at least 1.

vero plan

Flag Meaning
--graph print the resolved task graph: every needs edge with its origin, every holds claim, every resource
--only task with --graph: print only what vero run --only task would run; repeatable
--explain task explain one task: why it runs when it does, what it holds, what it sends
--strict treat lint warnings as load errors (exit 64)

Without --graph or --explain, a plan that loads prints one writes: line per fixture step, reset hook first, then a line that counts what the plan will send:

$ vero plan examples/rate-limit.yaml
ok: 4 tasks, 0 finally tasks; the plan sends 12 HTTP requests

The count is at most N when a when guard, a tag, a retry, followRedirects or a script step makes it a bound rather than a number, and , plus up to N from waitFor adds the attempts a waitFor step may make. A gRPC call and a WebSocket upgrade each count as one request in this line, as they do in the summary. A plan with script steps ends with one runtime <name>: <path> <version> line per runtime it found.

vero plan opens no socket to the target. It does run two checks that leave the process: it runs <runtime> --version for each script runtime, and it opens every Postgres connection a sql step reads through, to warn when the role could write. Those warnings are not lints and never become errors.

Warnings and advice go to stderr, one per line, as warning: <file>:<line>: <path>: <message> and advice: .... Under --strict a warning is printed as error (--strict): and the command ends with <file>: N lint warning(s) are errors under --strict; nothing ran, exit 64. A plan that does not load prints each error on its own line, then <file>: N load error(s); nothing ran, exit 64; the graph of a plan that would not run is never printed.

--graph

One block per task in plan order. Each needs edge names the task it depends on, the kind of edge (explicit, derived:template or derived:when) and the place in the plan that created it. Each holds line is a claim, not an edge: it orders nothing. After the tasks, every declared resource lists its holders, with a mode in parentheses only where a claim differs from the resource's default.

$ vero plan --graph examples/rate-limit.yaml
task login
task rate-limit-trips-on-fourth
  needs login derived:template at tasks.rate-limit-trips-on-fourth.steps[0].http.headers.Authorization
  holds ratelimit/orders-api-key exclusive
task pagination
  needs login derived:template at tasks.pagination.steps[0].http.headers.Authorization
  needs login derived:template at tasks.pagination.steps[1].http.headers.Authorization
  needs login derived:template at tasks.pagination.steps[2].http.headers.Authorization
  holds ratelimit/orders-api-key exclusive
task filtering
  needs login derived:template at tasks.filtering.steps[0].http.headers.Authorization
  needs login derived:template at tasks.filtering.steps[1].http.headers.Authorization
  needs login derived:template at tasks.filtering.steps[2].http.headers.Authorization
  holds ratelimit/orders-api-key exclusive

resource ratelimit/orders-api-key exclusive
  held by rate-limit-trips-on-fourth, pagination, filtering

A resource nobody claims prints held by nobody. With --only, the first line says what was selected and the rest is the graph vero run --only would run:

$ vero plan --graph --only pagination examples/rate-limit.yaml
# --only pagination: 2 of 4 tasks selected; left out: rate-limit-trips-on-fourth, filtering
task login
task pagination
  ...

--only without --graph is refused with vero plan: --only goes with --graph. When both --graph and --explain are given, --graph wins.

--explain

One task, in the vocabulary of --graph, plus what it will send and which paths --strict-paths would check:

$ vero plan --explain rate-limit-trips-on-fourth examples/rate-limit.yaml
task rate-limit-trips-on-fourth
  needs
    login derived:template at tasks.rate-limit-trips-on-fourth.steps[0].http.headers.Authorization
  upstream, transitively: login
  downstream: none
  holds
    ratelimit/orders-api-key exclusive, also held by pagination, filtering
  when
    no guard: it always runs once its needs pass
  steps
    1. http GET {{ env.baseUrl }}/orders, repeat 4 serial: 4 requests
  assertion paths (checked by --strict-paths): responses, responses[0:3][*].status, responses[3].status, responses[3].headers["retry-after"]

A task with no edges reads nothing: it can start as soon as a worker is free; one with no claims reads nothing: it can overlap any task. A guard is printed with the tasks it reads. A step with results lists them after its line, and a secret result says so: token = body.token (secret: scrubbed from every output once the step answers). Naming a matrix task by its base name prints <name> is a matrix of N rows: <rows>; explain one of them. An unknown name is vero plan --explain: no task <name> in the plan, with a suggestion when one is close, exit 64.

vero run

Flag Default Meaning
--jobs N 4 on this machine (GOMAXPROCS) tasks at once; --jobs 1 is deterministic against a deterministic server
--repeat N 1 run the whole plan N times against the same server and print the flake report (reading it)
--compare-jobs off with --repeat: N runs at --jobs 1, then N at --jobs, both pass rates, and a hint at a missing holds; needs --jobs 2 or more
--timeout D the plan's, or none run deadline; unfinished tasks end timed_out or not_run, and finally still runs
--finally-timeout D 30s deadline for finally tasks after the main graph, including after a run timeout or a signal
--strict off lint warnings are load errors (exit 64)
--strict-paths off fail a step when a path an assertion reads is absent from the result and not listed in the step's optional:; meant for CI
--include-tag tag none run tasks with this tag, which are excluded by default (slow); repeatable
--only task none run this task and every task it needs, transitively; repeatable. Tasks left out are not run, not counted, and do not affect the exit code
--events path or - none write the vero.events/v1 log (contract); - is stdout, and the human report moves to stderr
--events-body-cap N 65536 bytes of each body the event log carries; longer bodies are cut and marked bodyTruncated
--junit path none also write JUnit XML; blocked and not-run tasks become labelled <skipped>, and the exit code ignores the file
--tap path none also write TAP version 14
--html path none also write a one-file HTML report rendered from the event log; the exit code never reads it
--update-snapshots off write every matches snapshot file instead of comparing; each write fails its assertion, so the run exits 1 and is never the green one
--insecure-skip-verify off skip TLS certificate verification, for this invocation only
--ca-cert path system roots also trust the PEM certificates in this file, on top of the system roots; repeatable
--client-cert path, --client-key path none present this certificate to servers that ask; both or neither

The order of work is fixed. The job and repeat counts are checked, then the TLS flags, then the plan loads (a load error exits 64 and sends nothing), then --only is resolved (an unknown task is vero run: --only <name>: no task named <name>, with a suggestion). From there every byte of output passes through the secret scrubber: the terminal, the event log, JUnit, TAP and the HTML report. --html keeps a copy of the scrubbed event stream in memory even without --events.

What a run prints

A run starts with its id, the plan file, the job count and the lifecycle strategy, then any runtime lines, then a blank line, then the failures, then the summary:

run 01M3ZRXDWH5EWW49N2Z23TEBYY  examples/rate-limit.yaml  --jobs 4  lifecycle reset

4 passed  0 failed  0 errored  0 timed out  0 skipped  0 blocked  0 not run   (4 tasks, 12 requests, 0.0s)

A plan whose lifecycle sets sharedTarget adds a caveat: line under the header saying the run cannot be deterministic. The summary line always carries all seven counts, in this order, then the task count, the request count and the wall time. passed gains (N unverified) when a script step returned ok with no step events, and a run whose finally tasks ran ends with teardown: 1 passed 1 failed. The states are defined on States and exit codes.

Each task that did not pass gets a block headed FAIL, ERROR or TIMEOUT, with the task, the step and the failing assertion, the plan file and line at the right margin, the exchange, each assertion that did not hold with the values it read, and a timing line:

FAIL  wrong › step 1 › assert #1                                  plan.yaml:27

  GET http://127.0.0.1:18080/fixtures/status?code=503             HTTP/1.1 503 Service Unavailable  1.0ms
    > accept-encoding: gzip
    > user-agent: vero
    < content-length: 17
    < content-type: application/json
    < date: Sat, 03 Oct 2026 02:18:58 GMT
    < {"status":"503"}

  assert  status == 200    failed
          status = 503
          plan.yaml:27

  timing  dns n/a  connect 0.1ms  tls n/a  ttfb 0.7ms  total 1.0ms  (new connection)

A timed-out step adds deadline hit waiting for first byte, or whichever phase the deadline fell in, to its timing line. Blocked and not-run tasks get one line each, BLOCKED <task> <reason> and NOT RUN <task> <reason>. Failures in finally tasks come after the main ones under a TEARDOWN heading, and a reset hook that did not pass prints LIFECYCLE RESET FAILED: no task ran. Bodies longer than 2 KiB are cut with … (cut at 2 KiB of N bytes).

Before the summary line, the run reports what a reader would otherwise miss, one line each: fixture for every fixture step (rows written, committed or rolled back), waitFor with the attempts and time waited, unverified for a script that checked nothing vero could see, leak for processes a script left behind, retried for a step that needed a retry to pass, and only for what --only selected and left out.

Colour is used only when stdout is a terminal and NO_COLOR is unset. Passed is green; failed, errored and timed out are red; the rest are yellow.

--repeat and --compare-jobs

With --repeat N above 1, each run is one line, then the flake report:

$ vero run --repeat 3 examples/rate-limit.yaml
run 1/3  01M3ZRXDXGSXGG6G9QW70VH8MX  4 passed  0 failed  0 errored  0 timed out  0 skipped  0 blocked  0 not run   (4 tasks, 12 requests, 0.0s)
run 2/3  01M3ZRXDXMD7X7VNC8JYJXN0S9  4 passed  0 failed  0 errored  0 timed out  0 skipped  0 blocked  0 not run   (4 tasks, 12 requests, 0.0s)
run 3/3  01M3ZRXDXQ7YP0CGJ67JSMRFA3  4 passed  0 failed  0 errored  0 timed out  0 skipped  0 blocked  0 not run   (4 tasks, 12 requests, 0.0s)

3 runs of examples/rate-limit.yaml at --jobs 4, lifecycle reset:
  login                       3/3 passed
  rate-limit-trips-on-fourth  3/3 passed
  pagination                  3/3 passed
  filtering                   3/3 passed

When a run fails, the failures of the first failing run are printed once, under first failing run at --jobs N, i/N (<id>):, and a flaky task's line ends in FLAKY with the assertions that failed and the values observed. --compare-jobs runs the series twice, at --jobs 1 and at --jobs, prints the flake report of the wide series, then a table of both pass rates and the hint. Reading a flake report and the --jobs hint explains the output. The exit code of a repeated run is the worst code of any run. A signal ends the repeats and reports the runs so far.

Exports

--junit, --tap, --html and --events are written after the runs. A file vero cannot write is reported as vero run: <error> and the exit code becomes at least 2, whatever the tasks did. The HTML report is one file with no script and nothing loaded from elsewhere; it opens with the exit code and its meaning, lists tasks under their states with the failures in full, and carries the flake report after --repeat. vero report renders the same page from a saved log.

vero report

vero report --html out.html events.ndjson

Reads a vero.events/v1 log and writes the HTML report. --html is required (vero report: --html is required), and the log must be one file. A log with an event type or a field the contract does not know, or a different schema, is refused with the event's number, exit 64. Bodies the log cut are marked (body truncated) on the page.

vero import

vero import postman collection.json > plan.yaml
vero import hurl file.hurl > plan.yaml
vero import curl -- -X POST -H 'Content-Type: application/json' -d '{"a":1}' https://api.example.com/orders

The plan goes to stdout. What the importer invented is listed on stderr as added:, what it could not translate as not translated:, and when anything was left out the command ends with vero import: N item(s) not translated; the plan lists them at the end, exit 1. A file that does not read or parse is vero import: <error>, exit 64. Importing Postman, Hurl and curl has what each source translates to.

vero version

$ vero version
vero v0.0.0-20261003012337-3b2581e79d7c+dirty go1.26.8
module github.com/STNeto1/testing-platform
commit 3b2581e79d7c6d8bc8de86257b884196e4218c3d (modified)

The first line is the module version and the Go toolchain, which is also what run.start records as version in the event log. A binary built without module information prints vero (no build info).

TLS flags

Certificates belong to the environment a run targets, not to the plan, so they are flags. --ca-cert adds to the system roots rather than replacing them, so a run that trusts a private CA still verifies public hosts; a file with no certificate is --ca-cert <file>: no PEM certificate in the file. --client-cert and --client-key go together (--client-cert and --client-key go together; pass both or neither), and a key that group or others can read draws vero run: warning: --client-key <file> is readable by group or others (mode 0644); chmod 600 it. One TLS configuration serves the http, ws and grpc steps of the run.

This page is docs/content/reference/cli.md in the repository.

verodocs