opentide

JSON output

The JSON document every opentide command prints with --json, its envelope keys, failure codes and output modes.

With --json, every opentide command prints exactly one JSON document on stdout, whether it succeeds, fails, or was called with a typo. Logs go to stderr, at WARNING and above (everything with --debug), so opentide --json validate 2>/dev/null is always parseable. This page describes the keys every document shares. Each command's own keys are on its page.

--json works before or after the command: opentide --json validate and opentide validate --json are the same. So do --no-color, --debug and --quiet. Options that take a value (--repo, --data, --proxy) stay before the command.

opentide validate --json
opentide --json info --platform sentinel

The document is indented when stdout is a terminal, and one compact line otherwise: in a pipe, a file, CI or a coding agent's shell. OPENTIDE_JSON_INDENT=0 prints one line everywhere and OPENTIDE_JSON_INDENT=2 indents by two spaces everywhere; any other value is ignored. Either way the document ends in exactly one newline, so opentide --json validate | jq .summary and json.loads read it the same.

The envelope

Every document carries these keys next to the command's own. Keys that existed before keep their name, type and meaning; new information is only ever added under new keys.

KeyTypeMeaning
okboolfalse when the command failed
statusstrcompleted, passed, issues, skipped or failed
messagestrThe outcome in one sentence
errorstrOn every non-zero exit: the error message, as a string (the message unless the command names a more specific error)
warningslist[str]Present when the command has warnings
schema_versionint1. It changes only if an existing key ever changes meaning
commandstrThe command that ran: validate, validate query, generate docs rules. Empty when no command was recognised
duration_msintWall time of the command
exit_codeintThe process exit code, the same as $?
summaryobjectCounts behind the outcome, such as files or errors. Empty when the command reports none yet
next_actionslistCommands worth running next, as a JSON reader runs them: [{ "command": "opentide --json lint --fix", "reason": "…" }]
failureobject or nullWhy the command did not exit 0. null exactly when exit_code is 0

failure has five keys:

KeyMeaning
codeStable snake_case code to branch on. See below
messageThe error message (the same text as error on a failure)
argumentThe input at fault, such as --type, section or the mistyped command; otherwise null
suggestionThe closest valid value, such as validate for valdate; otherwise null
crash_logWith internal: the path of the redacted crash log, or null if it could not be written. Otherwise null

Failure codes

CodeExitMeaning
usage_unknown_command2No such command. suggestion names the closest one
usage_unknown_option2No such option for that command
usage_invalid_choice2A value outside the accepted ones: --platform, --type, --target, an info section, a generate phase, a --strategy
usage_invalid_value2A value that cannot be used, such as the removed --plan or a rules unreviewed --older-than that is not a window
usage_missing_option2A required option or argument is missing (--platform, --strategy, --technique, validate query without --live)
usage_needs_input2The command would prompt, but nobody can answer: --json, no terminal, or a coding agent. The message names the flags that answer the prompt
usage_tui_needs_tty2opentide tui ran outside the human mode or without a terminal on stdin and stdout
usage_error2Another usage error, such as options that cannot be combined, or a command group named without its subcommand (opentide --json mcp)
usage_watch_needs_tty2validate --watch under --json, a coding agent or OPENTIDE_OUTPUT=agent: it redraws a terminal until Ctrl-C, and nobody would see it
no_tenant_reached2validate query --live sent no query because no configured tenant takes the rules
needs_human_decision2migrate vocab-scope left values only a person can place
not_implemented2A reserved command, such as deploy metadata
share_unreachable2share: every failure was authentication or connectivity
share_partial3share: some objects were shared and some failed
share_failed1share: objects failed and none succeeded
scope_no_match, api_key_unset, …1share preflight failures; the code is the preflight value
reference_not_found2new: --objective or --threat names no object in the catalogue
path_exists1new: the file it would write already exists; it never overwrites
path_not_writable1new: the folder or the file it would write cannot be written (read-only checkout, permissions)
template_missing1new: generation ran but wrote no template or schema for the family
doctor_checks_failed1doctor: at least one check failed. Read checks for which
issues_found1The command failed on what it found: validate errors (or warnings under --strict), lint --strict findings, info --strict load errors. The findings are in the document
config_parse1A configuration TOML file does not parse. diagnostics holds the file, line and column
yaml_parse1deploy read a rule file whose YAML is not one mapping. diagnostics holds the file and line
not_runnon-zeroThe command was skipped and says why in message
command_failed1Any other failure
internal1An unexpected error: a bug in opentide. See An unexpected error

Exit codes are in Exit codes. opentide explain <code> prints the page for each failure code, as it does for findings (Diagnostic codes).

A usage error

A typo in a command, an option or a value exits 2 with the same document. Case and -/_ do not count against a suggestion: --type RULE suggests rule. Under --json nothing goes to stderr. In this tutorial repository, opentide --json info rulez exits 2:

opentide --json info rulez
opentide info rulez
{
  "error": "Unknown info section: rulez. Choose rules, threats, objectives, coverage, vocab.",
  "ok": false,
  "status": "failed",
  "message": "Unknown info section: rulez. Choose rules, threats, objectives, coverage, vocab.",
  "schema_version": 1,
  "command": "info",
  "duration_ms": "...",
  "exit_code": 2,
  "summary": {},
  "next_actions": [],
  "failure": {
    "code": "usage_invalid_choice",
    "message": "Unknown info section: rulez. Choose rules, threats, objectives, coverage, vocab.",
    "argument": "section",
    "suggestion": "rules",
    "crash_log": null
  }
}

Without --json the same error is one line on stderr, with the suggestion under it:

error: Unknown info section: rulez. Choose rules, threats, objectives, coverage, vocab.
  help: did you mean 'rules'?

An unexpected error

An exception no command handles is a bug in opentide. The run exits 1 and, under --json, still prints one document on stdout, with failure.code internal and failure.crash_log naming the log written for it. The message also goes to stderr, for the person reading the terminal. No command in a healthy repository prints this, so the sample is illustrative:

{
  "error": "RuntimeError: index exploded",
  "ok": false,
  "status": "failed",
  "message": "RuntimeError: index exploded",
  "command": "info",
  "exit_code": 1,
  "failure": {
    "code": "internal",
    "message": "RuntimeError: index exploded",
    "argument": null,
    "suggestion": null,
    "crash_log": "/home/me/.local/state/opentide/crash-20261023T110335102704Z-648.log"
  }
}

Where the log goes, what it holds and how to report the bug: Troubleshooting.

Output modes

opentide decides once per command who is reading. The first rule that matches wins:

OrderRuleMode
1opentide mcp startmcp: stdout carries nothing but JSON-RPC
2--jsonjson: one document on stdout
3OPENTIDE_OUTPUT is json, agent, plain or humanthat mode
4A coding agent is detectedagent
5stdout is not a terminal, NO_COLOR or --no-color, TERM=dumb, or CIplain
6Otherwisehuman

OPENTIDE_OUTPUT=json is the same as passing --json to every command.

plain is human without colour or animation. Lines are never wrapped, so a long path stays on one line.

agent is plain, plus: a command that would prompt exits 2 at once with usage_needs_input and names the flags that answer the prompt, and a failure ends with tip: add --json for machine-readable output. Agents are detected from AI_AGENT first, then CLAUDECODE, CURSOR_AGENT, GEMINI_CLI, CODEX_CI, COPILOT_AGENT, COPILOT_CLI, OPENCODE, AMP_CURRENT_THREAD_ID, AUGMENT_AGENT and CLINE_ACTIVE. Detection changes presentation only: it never relaxes a confirmation, a --yes requirement or a strategy check.

Human output

Without --json, a command prints its warnings first (warning: … on stderr), then exactly one outcome line, then at most three next steps:

warning: Query validation reported warnings for sentinel
✓ Query validation passed for sentinel

validate and lint print each finding as a code frame before the outcome line, and their outcome line counts them (✗ 2 errors in 1 file · 3 objects checked · 0.9s). See Diagnostic codes.

LineMeaning
✓ …Done
! …Done, with issues
Skipped: …Nothing was done, and why
error: …Failed (stderr), followed by indented note: and help: lines
→ …A next step: the command to run, and why

A console that cannot encode ✓, ✗ or → (a Windows code page, PYTHONIOENCODING=ascii) gets ok, x and ->.

The command tree

opentide --json commands describes every command an agent can run, with its options, types, choices, defaults, environment variables and an example. See Command tree for agents.

Output schemas

Each command in opentide --json commands carries output_schema: the JSON Schema of the document it prints, generated from the typed result models in src/opentide/cli/ui/results/. It is a $ref into the $defs of the same document, anyOf the command's own result and CommandError, the document of a usage error. To check a document against its command's schema:

opentide --json commands > commands.json
jq '{"$defs": ."$defs"} + (.commands[] | select(.path == "validate") | .output_schema)' \
  commands.json > validate.schema.json
opentide --json validate > validate.json
check-jsonschema --schemafile validate.schema.json validate.json

The schemas allow keys they do not name, because new keys are added without notice. A key a schema names keeps its type.

On this page