Skip to content

Scripting the CLI: JSON output, exit statuses, and help

The varlatch CLI is meant to be driven by scripts, CI, and coding agents as well as people. For them it offers JSON output on every read command, distinct exit statuses, and help that never fails.

Add --json to print one JSON document on stdout instead of the human format. Diagnostics stay on stderr, including the warning every command but agents hook prints first on a Node.js release without its line’s security fixes (Node.js version) and the credential expiry warning, so a script should not expect stderr to be empty. Every document has "version": 1; a change that removes or renames a field raises it. The human formats do not change either: scripts that parse them keep working.

Command Document
varlatch status --json stored credentials and the repository context; with --probe, each valid credential’s identity (id, name, kind) and organization (id, slug, name, or null for a person), where the server can say
varlatch whoami --json server, and the caller as the server answers: identity (id, name, kind, email), organization (null for a person), credential (id, name, kind, expiresAt; never the token), listener, and on the tailnet listener tailnet (the device)
varlatch context --json the resolved context
varlatch env list --json environments: name, tier, kind, selected
varlatch values list --json environment, items: name, sensitive, source, rotating (never a value)
varlatch validate --json environment, result (valid, invalid, incomplete), complete, missing, invalid, unresolved, notEvaluated, exitCode
varlatch import <file> --json file, dryRun, target, offline, items (name, type, sensitive, inContract, line), references, contract (newItems, revision), stored, deleted (never a value)
varlatch contract push --json revision: id, contentHash, active, semanticsVersion
varlatch contract show the active revision (always JSON)
varlatch org list --json organizations: id, slug, name
varlatch project list --json organization, projects: slug, name, contractAuthority
varlatch identity list --json organization, identities: id, name, kind, retired, lastSeenAt
varlatch identity reach <identity> --json organization, identity (id, name, kind, state, disabledReason, liveCredentials), organizationActions, projects (each with actions and environments, each with actions: action, grants, conditions), requirements (id, version, target, selector, condition), notes
varlatch credential list <identity> --json identity, credentials: id, kind, name, createdAt, expiresAt, revokedAt, lastUsedAt
varlatch credential issue <identity> --name <name> --out <file> --json identity, credential: id, kind, name, expiresAt, maxUses; out, the file that holds the token (never the token)
varlatch security report --json organization, credentialReview: generatedAt, idleDays, summary (reviewed, listed, neverUsed, idle, noExpiry), items (id, name, kind, createdAt, expiresAt, lastUsedAt, issuedBy, identity with id, name, kind, state, disabledReason, and reasons: never-used, idle, no-expiry; never a token), expiry (warningDays, summary with soon and expired, items as above with state soon or expired instead of reasons)
varlatch security status --json organization, enforceAvailable, controls (availableModes, window with days, since, generatedAt or null, items: each control as in security controls plus explanation with report and enforce, and counts with total, currentHour, pending, entries, or null), approvalKeys (summary with admins, withTrustedKey, withoutTrustedKey; items: identity, trusted, unconfirmed, lastConfirmedAt, passkeys with name, trust, trustedVia, createdAt, trustedAt; never key material), credentialReview (as in security report), requirements (count, protectedBy, items), checklist (id, title, why, url), unavailable (section, reason not-permitted or not-supported); a part that is unavailable is null
varlatch security controls --json organization, items (control, mode, config with defaultTtlSeconds and maxTtlSeconds or null, version, changedAt, changedBy), availableModes
varlatch security set <control> <mode> --json organization, control (as in security controls), changed, loosened
varlatch audit list --json organization, events: the audit events as the API returns them
varlatch tailnet requirements --json organization, requirements: as the API returns them
varlatch scan --json findings and files not scanned; see secret scanning
varlatch sync check --json result (match, drift, incomplete, unchecked), platform, base, destination, environment, keySet, counts, keys (name, destination, status, reason), exitCode (never a value); see sync check
varlatch doctor --json the installation health report
varlatch self-update --check --json the available release
varlatch login --start --json verificationUri, userCode, expiresAt (never the sign-in’s private device code)
varlatch login --wait --json state: signed-in (with credentialId, expiresAt), pending, denied, expired, or consumed (with credentialId)

varlatch audit export always prints NDJSON.

Status Meaning
0 Success.
1 Any other failure: a server error, a file that cannot be read, a failed write.
64 The command line is wrong: an unknown command or subcommand, a missing argument, flags that conflict, or a value given the way assisted mode refuses. Checked before any credential is needed, so it is 64 whether or not you are signed in. Nothing was changed.
69 The server cannot be reached, or is in maintenance or overloaded (502, 503, 504), including an error page from a proxy in front of it. Try again later.
75 varlatch login --wait reached its deadline while the sign-in still waits for approval. The sign-in is kept: run --wait again. Also: a change waits for your approval and the CLI does not wait for it (assisted mode), or the approval expired before anyone approved it. Nothing was changed.
77 Not authenticated, or the server denied the request (401, 403). Also: an approval was denied or cancelled, was already used, or does not fit the request.
130 Ctrl-C stopped the wait for an approval; the CLI cancelled it. Nothing was changed.
78 Strict startup found a violation, or assisted mode cannot mask a Secret shorter than 8 bytes. The command was not started.

These keep the meanings they always had:

  • varlatch validate: 1 when the environment is invalid, 2 when not every item could be evaluated with this identity’s access. With --json too.
  • varlatch scan: 1 for findings or when the scan could not run, for any reason; 2 when some files were not scanned.
  • varlatch types --check: 1 when the file is stale or missing.
  • varlatch sync check: 1 for drift (a key differs, or is missing on or extra on the platform); 2 when not everything could be checked and no drift was found. With --json too. See sync check.
  • A command whose change needs your approval: 0 once you approved and the change was made; 75 when the approval expired, or in assisted mode as soon as the address is printed; 77 when it was denied, cancelled, closed by the host operator’s reset or does not fit; 130 for Ctrl-C. Only the server’s summary, the address and the expiry are printed, on stderr.
  • varlatch login --wait: 0 once the credential is stored; 75 when the sign-in is still waiting for approval at the deadline (60 seconds, or --timeout up to 600); 77 when it was denied, expired, or already collected; 69 when the server cannot be reached (the sign-in is kept). It never exits 0 before a credential is stored.
  • varlatch upgrade and varlatch self-update: 69 when GitHub cannot be reached in 30 seconds or answers with a server error (5xx); 1 for any other failed release lookup, such as a release that does not exist.
  • varlatch request: 0 when the destination answered, whatever the HTTP status (as curl), and the response was written whole; 1 when the Broker refused the request, or the response was cut off or could not be written; 77 when the Broker did not accept the per-run proxy credential; 69 when the Broker cannot be reached.
  • varlatch run: once the command has started, the command’s own status (128 plus the signal’s number when a signal ended it). Before that, the statuses above: a run refused for its flags exits 64, one that cannot authenticate 77. Its own options, before --, are checked strictly in every mode before anything is fetched or started: an unknown option (a misspelling such as --allow-unmask, or --environment=<name>, which is not supported), an option without its value, an option given twice (--allow-unmasked, --allow-inherited, --allow-host, --target, and --omit repeat), or an argument before -- exits 64. So does an --omit name that is neither stored in the environment nor in its Contract, checked before the command starts (in a default run, before any Secret is disclosed). Everything after -- goes to the command unchanged.

A machine credential (the kind varlatch credential issue and the dashboard issue for a machine) says so in its last 7 days before its expiry, however long it was issued for, on every API response to a request it authenticates, errors included: the response carries a Varlatch-Credential-Expires header with the expiry as an ISO 8601 UTC timestamp, such as 2026-11-17T12:00:00.000Z. Nothing renews such a credential but a person. Credentials without an expiry or expiring later get no header, and neither do unauthenticated requests. Nor do credentials that are renewed by design: a person’s dashboard sessions and varlatch login sign-ins, OIDC exchanges, and agent-safe runs. A program that calls the API directly can read the header to warn ahead.

When the CLI authenticates with VARLATCH_TOKEN and the server sends the header, it prints one warning on stderr per command, never on stdout:

varlatch: credential for https://vault.example.com (VARLATCH_TOKEN) expires 2026-11-17T12:00:00.000Z; issue a new one (`varlatch credential issue`) and replace VARLATCH_TOKEN before then

varlatch run prints it before the command starts, so the command’s own output is unchanged, and stdout of every command, varlatch env list included, stays exactly as without the warning. Inside an agent-safe run the CLI does not warn: the run’s own credential ends with the run. A credential stored by varlatch login keeps its own warning, from the expiry recorded at sign-in, and a command warns at most once either way.

varlatch login --server <url>, the browser sign-in, prints two lines on stdout, then waits for the sign-in page to call back:

Complete passkey sign-in in your browser:
https://vault.example.com/enroll?callback=http%3A%2F%2F127.0.0.1%3A50123%2F

The second line is the address, indented by two spaces. These lines do not change, so an app that drives the sign-in can read the address from them.

The CLI also opens the address in the default browser: with open on macOS, xdg-open on Linux and other systems, and the URL handler, rundll32 url.dll,FileProtocolHandler, on Windows. An app that opens the address itself sets VARLATCH_NO_BROWSER=1. With VARLATCH_NO_BROWSER set to anything but an empty value or 0, the CLI opens nothing and prints the same lines. An older CLI ignores the variable and opens the browser as well, so an app can set it whatever the CLI’s version.

  • varlatch --help, varlatch -h, varlatch help, and varlatch alone print the usage on stdout with status 0.
  • varlatch <command> --help and varlatch help <command> print that command’s entries, also with status 0, and contact no server.
  • A --help after -- in varlatch run -- <command> belongs to the command.
  • An unknown command exits 64, with the usage on stderr.