JSON output
Documentation/Native client workflows
JSON output20 min read

JSON output for automation

On this page

Five of the six shipped executables (scribe, scribe-server, scribe-auth, scribe-gateway, and scribe-ctl) and the development tool scribe-perf accept global --json, before or after a subcommand. scribe-runner has no JSON command results apart from build info; it supports --log-format json for its logs. Read stdout as one JSON object and also check the process exit status. Diagnostics and progress stay on stderr (or the configured service log files).

--log-format json independently selects JSON lines for diagnostic logs. It neither enables JSON command results nor guarantees that every stderr message is JSON. Do not merge stderr into stdout when parsing command results.

Results

Command results retain their command-specific fields:

ExecutableJSON results
scribeRepository, workspace, path, history, transfer, lock, and merge reports; simple confirmations use message.
scribe-serverEvery command, including initialization, certificates, auth administration, backup/restore, metadata import/export, migration, and health/space reports.
scribe-authinit paths (data_dir, admin_credential_file, pepper_file, revision_file, trust_domain), certificate paths/fingerprint, migrate schema reports (data_dir, from, to, users, servers, dropped, dry_run), restore results (data_dir, revoked, kept_server_credentials, revision, incarnation, admin_credential_file, admin_credential), admin-credential issue results (data_dir, user, credential_file, credential, expires), and administrative rows arrays. No password, private-key, or pepper contents are returned.
scribe-gatewayExample TOML in content, initialized data_dir, and the status inventory with observation timestamps.
scribe-ctlCommand results include command and ok; cluster snapshots (with the active binary set as binaries), setup, add, upgrade, renew and adopt plans and results (a rolled-back upgrade adds rolled_back, failure, rollback, active_set and backups; a renewal adds renewed, per credential holder, credential, file, replaced, replaced_expires_ms, new and expires_ms, certificates, per server certificate service, file, key (the private key file a rekey replaced, else null), change (reissue, handover, rechain or rekey), replaced_expires_ms and expires_ms, and rotation, retired and authority for CA changes, or when rolled back rolled_back, failure, rollback and kept; cluster snapshots add ca_rotations; an adoption plan adds adopt (the old projects with the services that running, the web database volume, was_running), and a rolled-back adoption rolled_back, failure, rollback and projects; nothing to do is unchanged: true), access rows (objects with account, kind, state, scope, access, detail), doctor reports (sections of checks with id, status, detail, fix and for expiries expires, and a summary; see Controller results), bootstrap results (remote, plan, build, target, controller, unchanged, current, steps), summaries (a docker cluster's add docker: project, directory, files, images, command; and with the web interface web: url, tls_dir, tls), and log lines have their own fields. Plan steps name their data root as service (auth, a store, web). A command run on a remote cluster prints the remote scribe-ctl's result unchanged, except doctor, which merges it into its own report.
scribe-perfMeasurement helper: info returns build/format metadata; workload, probe, and objects return ok and measurement with raw latency samples. The JS scenario harness writes performance reports and compares baselines.

Administrative rows are arrays of arrays of strings, in the same column order as text mode's tab-separated rows, including pagination cursors. An empty result is {"ok":true,"rows":[]}. Secret contents remain in their designated files: credentials create returns one row [credential id, file], and the device key and certificate stay in the credential file. migrate counts accounts kept (users), servers given ids (servers), and token and session records removed (dropped), or with dry_run what a run would do; from is 3 and to is 4. A directory already at schema 4 reports from and to 4 and zero counts. restore reports the credentials it disabled, the server credentials kept with --keep-server-credentials, the new revision and incarnation (32 hex digits), and the id of the recovery administrator credential. expires is Unix milliseconds.

scribe whoami --json returns server, status, username, expires_ms, and verified (always false: the check is local). status is active, expired, login_pending, logout_pending, anonymous, credential_file (a --credential-file or SCRIBE_CREDENTIAL_FILE is selected; its identity is in the file), plaintext, or none. username is set for active, expired, and login_pending; expires_ms only for active.

scribe diagnostics --json returns ok, offline, versions, platform, connection, paths, workspace, and issues. versions holds client, tcp_protocol (2, plaintext tcp://), tls_protocol (4, mutual-TLS tls://), workspace_schema, and server (always "not checked (offline)"). Unavailable or unconfigured values are null. Inspection problems retain the available report and return exit status 1; see client diagnostics.

Server maintenance reports use named numeric fields for counts and sizes. verify, doctor, usage, and rebuild-locator include ok and exit alongside findings, even when problems are found; they do not append a second error object. rebuild-locator lists per-repository indexed, packed, loose, packs, skipped entries (pack, offset, id, detail; pack and offset are null for a loose file), and verification problems.

backup returns files, bytes, destination, manifest metadata, catalog_needs_recovery, warnings, and one findings entry per copied repository. catalog_needs_recovery is true when the root catalog was not closed cleanly and repositories were listed from the directory instead. Each finding contains repository, id, nullable open_pack, uncommitted_tail_bytes, uncommitted_packs, staging_leftovers, and needs_recovery. needs_recovery is true when the repository database was not closed cleanly, for example after a killed server; the head is then reported unknown and the restored server recovers the copy at first start. Nonzero leftover values describe an interrupted publication that was copied unchanged, not repaired.

migrate returns ok, dry_run, target, changed, pending, and repositories. Each repository includes its id in repository, its descriptor schema before the run, and the ordered steps selected by preflight. pending means the initial plan contained work; changed means that work was applied (always false for a dry run). After success, rerunning returns both false and empty steps for each repository. An interrupted or failed run exits nonzero; rerun to inspect and resume the durable progress. Incompatible pre-snapshot development formats are refused.

staging-branches returns data_dir, ok, the total staging count, and repositories with a staging array of name, branch (id), operations (pending branch-operation ids), rows (staged metadata rows), nullable source and source_change, created_change, and nullable age_at_most_ms. No creation timestamp is recorded for an unfinished branch, so the age is measured from the commit its snapshot was pinned to and is an upper bound. abandoned is null for a listing; after --abandon it contains repository, repository_id, name, branch, operations, and rows_removed, and the repositories array describes the state the command left behind.

Long-running serve --json commands emit {"ok":true,"state":"stopped"} only after normal shutdown. Readiness comes from the runtime directory and diagnostic lifecycle events. Similarly, scribe-ctl up --foreground --json completes its result after shutdown; use up --daemon --json to return when ready.

The JS check and scenario runners accept repeatable --report FILE outputs; the extension selects JSON, text, or self-contained HTML. Performance reports use schema 2 and kind: "performance"; comparison requires complete passing reports with compatible run metadata and every budgeted metric present. Missing or invalid required metrics fail comparison. An infinite ratio (nonzero current value against a zero baseline) is represented as null; its regressed boolean still reflects the regression policy. See test reports.

Controller results

Each dispatched controller command writes one document with command and ok. Selected-cluster results include cluster. With multiple clusters and no selector, status --json returns a clusters array; one cluster or an explicit selector returns a single snapshot. up, down, and status include status, generation, services, logs_dir, and warnings: credentials and certificates that expire within 30 days (or already did), or whose expiry cannot be read, each with what, path, expires_ms, and message. Service pid and endpoint values may be historical; inspect lifecycle status before treating them as live.

setup --plan --json returns plan_only: true and plan: operation, cluster, variant, definition, runtime, endpoints, checks (name, ok, detail), steps (an action name, its typed fields, why, and any set_aside paths), notices, resumes (the interrupted run it continues), and ready. A plan that cannot run has ok: false and exit status 1. A completed setup --json returns the same plan, results (per step: step, action, messages), and cluster_info, the summary file it wrote. add auth and add gateway return the same shapes (operation is add-auth or add-gateway); their plans also carry definition_diff (lines starting with @ FILE, +, -, a space, or … between hunks) and afterwards (what clients and operators do next). An add that finds its service complete returns unchanged: true, an empty results, and runs nothing. where --json returns endpoints (each tls:// one with the ca_fingerprints clients verify it by), sections of files (path, purpose, exists), web_tls, next commands, and warnings; with several clusters and no selector it returns a clusters array. doctor --json returns cluster, definition, runtime, remote (the target of a remote cluster, else null), sections and summary. Each section has id, title, host (null where the command ran; the remote target for the sections a remote cluster's own scribe-ctl reported) and checks; each check has a stable id, a status (ok, info, warn or fail), a one-line detail, and for most that are not ok a one-line fix. A check that judged an expiry (expiry.<file>, ca.internal.<purpose>) adds expires: at (RFC 3339, UTC) and days_left (whole days, negative once past); a certificate that does not expire has none. summary counts ok, info, warn and fail, names the worst status, and gives the exit status: 0, 1 (warnings only) or 3 (a failure). ok is false only when a check failed. A remote cluster's report is one document: this machine's sections, then the remote's. logs includes service and a lines array of original log strings. example --json includes format: "toml" and the template in content. Foreground up --json emits its result only after shutdown; daemon mode returns when ready. Operational errors use ok: false, command, and error alongside a nonzero exit status.

Errors, help, and exit codes

With --json, argument errors produce a single object containing ok: false, code: "usage", error, and exit: 2. The message deliberately omits argument values, which could contain a mistakenly supplied secret. Use text --help for syntax details. --json --help and supported --json --version requests return ok: true, content, and exit: 0.

For argument errors to use JSON, an exact --json must occur before the -- end-of-options marker. After that marker, --json is a positional argument.

Runtime failures have error, code, and exit. The client retains its domain error codes (which may be null), including merge_incomplete for a merge submission that does not cover every source change; logging setup failures use logging. For a semantic refusal error keeps the full name (number): detail form while the text-mode stderr line shows detail (name) without the number. Other executables use command_failed. Successful client workflows use command-specific result schemas; do not require an ok field in every client result.

  • Most commands exit 0 on success and 1 on runtime failure.
  • scribe retains its documented domain-specific exit codes.
  • scribe-server doctor and usage exit 1 for reported problems and 2 when the check cannot run, including configuration or ownership-lock failures.
  • scribe-server verify exits 1 for problems or inability to run.
  • scribe-server rebuild-locator exits 1 when entries were skipped, verification reported problems, or the command could not run.
  • scribe-perf exits 2 for failed measurement gates and 1 when it cannot run.
  • Argument errors exit 2 for every executable.

The process exit status is authoritative. A failed mutating command can have completed some work before encountering an error; JSON does not change retry, operation-outcome, durability, or recovery semantics.

File log entries include path and nullable moved_from (path, change). Continue with both fields from the last row: --before <change> --after-path <path>. With log PATH --all-branches, every row also has history_snapshot (repository change ceiling) and in_ancestry (boolean). Reuse the snapshot as --at when paging. true means the revision's accepting commit is in the selected branch's ancestry, not that its bytes were chosen by a merge; false identifies related work outside that ancestry. Both fields are null for ordinary logs and info's latest revision. Branch IDs and historical paths identify the revisions; branch display names are current, even with a pinned snapshot. Every log row carries the submitted message and a nullable note (change, revision, author, text, time): the current description recorded after acceptance, when there is one. The page's notes_available is false against a server that predates descriptions; every note is then null. describe CHANGE -m TEXT returns the recorded note; describe CHANGE --history returns change and notes, oldest first, each in the same shape. lock rules [path] returns source: "main:.scribe/config.toml", change, text, ordered rules: [{line, pattern}], and nullable path, required, and rule: {line, pattern}. Revision zero with text: "version = 1\n" means the main config file is absent. text is the complete accepted TOML configuration; rules contains only the lock section, with actual TOML source lines. Profile-only edits also advance the reported config-file revision. info additionally returns lock_required, lock_policy_change, and nullable lock_rule.

Offline edits return pending_reconciliation, an array of paths, alongside the usual opened and skipped arrays. Local add/delete reports use the same field, plus check: null with --offline or when nothing was opened (and always for edit, whose lock already made the round trip), {skipped: reason} when the server could not be reached or the check could not run, otherwise head, ready, and paths in the shape reconcile returns, for the opened paths only. A check with findings does not change the exit status. add exits 1 after its report when a file named on the command line appears in skipped; skips discovered while recursing into a named directory leave the exit status 0. revert and sync reports carry displaced (path, recovery) for every local copy moved to .scribe/_state/recovery; the same notice is printed on stderr in text mode. Status path entries have boolean needs_reconcile; info has nullable needs_reconcile (null when not opened). False is not a live ownership guarantee.

reconcile [--scan] returns head, policy_change, ready, exit, paths, and nullable discovery. Each path has path, action, ready, nullable base_change and server_change, lock_required, and issues. Issues contain code and detail; codes are server_changed, locked, missing, unexpected_file, unresolved, and move_source_blocked.

discovery contains tracked_modified (discovered and opened), tracked_missing, untracked, ignored, skipped_links, invalid_names (path, reason), and problems. Missing/untracked discoveries remain outside opened intent. ready and exit refer to opened paths: exit 0 when all pass, 1 when any are blocked. A blocked reconciliation emits this single report without an appended error object. An execution failure instead uses the normal runtime error schema; local intent and already-acquired locks survive for retry.

get returns repository and branch IDs, the requested revision and path, file_change (that path's last change at the selected snapshot), absolute destination, full content digest, size, and bytes_fetched (chunk payload bytes). Success means the complete file was verified and published. A refusal or failed download emits only the normal error object; no partial success report is printed. An existing destination is a protection refusal (exit 2).

merge and cherry-pick exit 1 while any path has applied: false; the report is still the only stdout document. Merge/resolve path entries include nullable group, rename, and path_conflict. group identifies paths whose choices are saved together. rename contains base, ours, and theirs historical names (a null side means deleted). path_conflict is rename/rename, rename/delete, or destination collision, or null for ordinary/content-only resolution. A resolved row retains this context; inspect choice and applied for its current resolution state.

scribe move --detect --json returns dry_run, moves (each source, destination, size, and id of the recorded move, null on a dry run), unpaired (each path and reason) for missing tracked files that no untracked file could be vouched for, and hashed, the number of untracked files whose length matched and were hashed.

Build info

scribe, scribe-server, scribe-auth, scribe-gateway, scribe-runner, and scribe-ctl answer a hidden --build-info flag for tooling that checks a set of binaries before an upgrade (scribe-ctl upgrade does). It must be the only argument: it prints one JSON object on stdout and exits 0 without reading configuration, taking locks, or starting logging. With any other argument it is an ordinary usage error.

sh
$ scribe-gateway --build-info
{"build":"0.1.0","executable":"scribe-gateway","formats":{"auth_protocol":2,"route_store_schema":2,"tcp_protocol":2,"tls_protocol":4},"target":{"arch":"aarch64","os":"macos"},"version":"0.1.0"}

version is the package version and build the SCRIBE_BUILD_ID set at compile time, or the version when none was set. target names the operating system and architecture the binary was built for, as Rust's std::env::consts spells them. formats maps each persistent format and protocol the executable reads and writes to its version, and lists only those it uses:

KeyMeaningReported by
tcp_protocol, tls_protocolStorage protocol on tcp:// and mutual-TLS tls:// connectionsall but scribe-auth
auth_protocolAuth wire protocol (SAU<n>)all
workspace_schemaNewest workspace schema (schema 1 is still read)scribe, scribe-runner
catalog_schema, repository_schema, object_store, envelopeData-root formats, as the data-root descriptor records themscribe-server
repository_migrationRepository metadata migration levelscribe-server
auth_schemaAuth store schemascribe-auth
route_store_schemaGateway route store schemascribe-gateway
ctl_stateFormat of scribe-ctl's binary-set and backup records in its state directory (bin/current, bin/<build>/SET.json, backups/<stamp>/<root>.json)scribe-ctl

Unified status

status and scan return workspace (id, repository, repository_name, branch, branch_name, view, view_profile, pending_view), synced (local sync watermark), scope (opened, scan, scan+verify), exit, and one canonical-path-sorted paths array. branch_name is the branch's name as the working copy last learned it (recorded at creation and switch, refreshed by --remote and by renames); it is null only for a working copy that has never learned it. There is exactly one row per path; the former separate opened, tracked_modified, tracked_missing, untracked, conflicts, deferred, and lock_status status lists are replaced directly in this unreleased interface. Other commands retain their own documented schemas, including reconcile's discovery categories.

Each path contains:

  • path, tracked, nullable action (add, edit, delete, keep), nullable local state (unchanged, modified, missing, present, not_file), untracked, and nullable inspection problem. Null state means uninspected or failed inspection.
  • needs_reconcile and deferred, independently of opened action/local state.
  • Nullable moved_from: {path, change}, moved_to paths, and move_operations IDs. Accepted move provenance stays separate from physical pending operations.
  • Nullable resolution containing state (unresolved, awaiting_application, applied), choice, applied, nullable group, path_conflict, and rename, using the merge/resolve meanings above. An applied choice retains its context.
  • Nullable lock: null means unknown, never unlocked. On successful remote checks this contains required, nullable rule: {line, pattern}, ownership (unlocked, ours, other), and nullable holder with author, workspace, branch, and generation. Ownership compares workspace IDs.

remote.state is not_checked, available, or unavailable. Available includes head, policy_change, and incarnation_matches; unavailable includes error. A failed explicit remote check retains all local rows, supplies no partial lock observations, and sets exit: 1 with process exit 1. It emits one report, without an appended error object. Lock-only rows have null local state. These observations are not an atomic server snapshot or a submit-readiness guarantee.

summary counts paths, opened, unresolved, awaiting_application, needs_reconcile, and deferred; categories can overlap. workflows contains nullable project_publication (operation id), nullable resolution (planning, aborting, conflicts, applying, resolved), boolean cherry_pick (the active resolution is a cherry-pick rather than a merge), nullable sync_target, and booleans switching and branch_operation_pending. pending_submits lists operation IDs with unknown outcomes. moves remains in operation order, with id, dependencies, source, destination, complete, aborting, and files (count). hints contains human-readable guidance; use structured fields, not these strings, for program logic.

ignored, skipped_links, invalid_names (path, reason), problems, and files_scanned describe discovery. files_scanned counts additional tracked paths checked by scanning beyond those already inspected for local workflows. No clean or ready_to_submit claim is made from a partial scan. Local path inspection or ignore-rule problems produce exit 1 while retaining the report; changes, conflicts, and pending workflows alone produce exit 0.

Workspace administration

workspace list --json returns a workspaces array, empty when no active registrations match. Each row contains repository (ID), repository_name, workspace (ID), branch (ID), nullable owner (principal ID), label, tags (array of name or name=value strings, possibly empty), created_ms, and boolean has_locks. IDs are full hexadecimal strings; created_ms is milliseconds since the Unix epoch. A null owner means an unowned registration on a server without auth. Labels and creation times are not last-active or online indicators.

The CLI follows inventory pages internally and returns one combined array. Repository or permission failures produce the normal error document rather than a successful partial inventory. --everyone changes owner selection and --all-repos changes repository selection; JSON does not bypass authorization.

Successful workspace remove --json returns repository, workspace, operation (all full hexadecimal IDs), and retired_ms (Unix milliseconds). Rejections, including held locks, use the normal error schema. After a connection failure, repeat the command with the same per-user configuration directory to resolve its saved removal intent. See workspace administration.

Repositories and workspaces

repo create and repo info return result ("created" or "info") and a repository object with id, name, main_branch, head, incarnation, created, and storage_compression; repo list returns the same objects in repositories.

workspace create, workspace register, workspace info, workspace config, and workspace view show return root, workspace_id, repository, repository_name, branch (id), branch_name, server, view, view_profile, pending_view, author, tags, readonly_mode, transfer_compression, synced, and incarnation. branch_name is the name the working copy last learned from the server: create and register fill it in, switch and status --remote refresh it, and the offline commands (info, config, view show) report the stored value, null only when none was ever learned. switch returns the sync report with branch and branch_name added.

Workspace views and profiles

workspace info, workspace view show, and workspace creation include view_profile (nullable originating profile name) and pending_view (null or an object with view, profile, and target). The view array always describes the active selection; an empty array means the entire repository. status and scan include these fields inside their workspace object.

workspace profiles returns revision and a profiles array of {name, view} objects from the accepted definition at the branch head. Local profile overrides are reflected in workspace view, not in the shared profile list.

workspace view set/add/remove, workspace view --continue, and workspace view --abort (alias cancel) return the sync-report schema, including complete, failed, deferred, and displaced. An incomplete transition exits 1 and emits one report, without appending a second error object. sync --json has the same behavior for incomplete file application. Use workspace view show to inspect the saved transition; resume with sync or --continue, or abandon it with --abort.

Diffs

diff [--revision N] [paths...] returns revision (the pinned change, or null for the baseline comparison), compared (files examined), changed (files whose status is not unchanged), and files. Each file has path, base_path (the origin of a pending move, otherwise the same path), nullable opened action, status, added and removed line counts, and diff: the unified hunks (@@ headers and +/-/ lines, without the ---/+++ file header) for a text change, otherwise null. status is unchanged, modified, added (no base side), deleted (no working-copy file), missing (neither side), binary (a NUL byte in the first 8 KiB of either side), or too_large (either side above the 2 MiB review bound). The exit status is 0 whether or not differences exist.

Controller live inventory

scribe-ctl df (alias usage), repo list, and status --storage emit one object with command, ok, cluster (the selected name, or null), and clusters. Each cluster contains cluster, ok, servers, totals, and lifecycle (null except with status --storage). Each server has name, endpoint, ok, error, instance, used_bytes, available_bytes, and repositories; repository rows have id, name, head, and bytes. Size fields and instance are null on failure, and the repository array is empty. Unknown available space is also null. Totals contain servers, responding_servers, repositories, used_bytes, and complete. Available space is never summed across potentially shared filesystems. Partial inventory retains successful servers, sets ok and complete false, and exits 1 without emitting a second error object.

Repository mount reports

workspace mounts returns { change, mounts }; each declaration contains name, path, repository, branch, revision (decimal string), and selected. It does not resolve target permissions. Sync reports add mounts entries with the same declaration fields plus state (ready, excluded, unavailable, retained) and nullable detail. Retained directories have empty identity/pin/name strings because they no longer have a declaration. Failed selected mounts also appear in failed, keep complete: false, and return a nonzero command exit status.

Project orchestration reports

project status returns configuration ("working_copy", or "publication_journal" if interrupted config replacement temporarily left it missing), exit, components, and optional publication. Components contain name, path, selected, problem, optional mount (repository ID, branch ID, decimal revision string), submitted (decimal revision string or null), and optional local status. Local status includes repository/branch IDs, decimal-string synced, scope, remote state (with decimal-string head/policy revisions when available), view and pending view, per-path status, pending operation IDs, moves, sync target, resolution, branch operation/switch state, and project ownership. Per-path and pending-view records reuse ordinary status fields and their existing types. Only selected components affect the aggregate inspection exit code. Opened work and pending publication alone are not inspection failures.

project publish (including --continue and --abort) returns id, complete, aborted, problem, and ordered components. Each participant contains name, path, repository/branch IDs, decimal-string revision, operation ID, and state: planned, prepared, outcome_pending, accepted, unchanged, rejected, or retired_after_restore. The parent has path . and is last. Exit status is nonzero while incomplete or when cleanup reports a problem. A successful abort preserves accepted history and returns aborted: true.

project sync uses the existing sync/mount report. project update returns the normal message confirmation; it stages a pin and does not submit. Ordinary status.workflows adds nullable project_publication and gives root-level recovery hints before suggesting ordinary submit retries.

Partial-view integration reports

Partial-view integration reports add outside_view to each resolution path and to status.paths[].resolution. This flag derives from the saved workspace view. An applied excluded path represents a saved metadata decision, not a local file. Unresolved excluded paths block submission just like included conflicts.

Resolution reports also expose the pinned source (branch, change) and merge_base, or null without an active integration. These identify the exact selection to restart after expanding a view. For cherry-pick, merge_base is the selected change's first-parent revision.

Source docs/json-output.mdSnapshot 2b8dcccb