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.
JSON output
Section titled “JSON output”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.
Exit statuses
Section titled “Exit statuses”| 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--jsontoo.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--jsontoo. 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--timeoutup 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 upgradeandvarlatch 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--omitrepeat), or an argument before--exits 64. So does an--omitname 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.
Credential expiry warnings
Section titled “Credential expiry warnings”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 thenvarlatch 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.
Browser sign-in from an app
Section titled “Browser sign-in from an app”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%2FThe 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, andvarlatchalone print the usage on stdout with status 0.varlatch <command> --helpandvarlatch help <command>print that command’s entries, also with status 0, and contact no server.- A
--helpafter--invarlatch run -- <command>belongs to the command. - An unknown command exits 64, with the usage on stderr.

