Machine-readable runs: --output json and the exit-code contract
OrchStep auto-detects pretty vs plain output, emits a single clean JSON document on demand, writes a result file alongside human logs, and follows a documented exit-code contract so CI can branch on the kind of failure.
A task runner that only prints pretty boxes is a task runner you cannot script. The moment OrchStep runs in CI, you need two things: output a machine can parse, and an exit code a machine can branch on. OrchStep gives you both, with sane defaults that need no flags at all.
Output that knows where it is running
orchstep run renders in one of three modes, and it auto-detects which to use:
a real terminal gets pretty; a pipe or a CI runner gets plain. You rarely
set it by hand.
| Mode | When | What you get |
|---|---|---|
pretty | default on a TTY | interactive box-drawing output |
plain | default when piped / in CI | clean line-oriented text, no boxes |
json | opt-in | a single structured document, streaming suppressed |
orchstep run deploy # pretty on a terminal, plain when piped
orchstep run deploy --output plain # force plain
orchstep run deploy --output json # structured resultJSON you can pipe straight into jq
--output json prints exactly one JSON document on stdout — nothing else —
so it pipes cleanly into jq. The shape is small and stable: workflow,
task, status, and a steps array, each step carrying task, name,
status, and output.
orchstep run show --output json | jq '.'{
"workflow": "envtest",
"task": "show",
"status": "success",
"steps": [
{ "task": "show", "name": "a", "status": "ok", "output": "..." },
{ "task": "show", "name": "b", "status": "ok", "output": "..." }
]
}So a gate is a one-liner:
orchstep run deploy --output json | jq -r '.status' # -> success
orchstep run deploy --output json | jq '.steps[] | select(.status=="failed")'When a run fails, the document still parses — status becomes failed and the
offending step's status is failed too, so you can pinpoint it
programmatically instead of grepping logs.
Logs and a result file: --json-file
--output json is exclusive — it suppresses the logs so stdout is pure JSON.
But in CI you usually want both: human-readable logs in the run view and a
machine-readable artifact for a later step. That is what --json-file is for —
keep a normal output mode and write the structured result to a file:
orchstep run deploy --output plain --json-file result.json
# stdout: the full plain logs (visible in the CI run)
# result.json: the same RunResult document, for a later step / artifactCritically, --json-file is written even when the run fails, so a
downstream step can always inspect what happened. Verified: a failing run still
produces a result.json with "status": "failed" and the failed step named.
The exit-code contract
Here is the part that makes CI gating precise. orchstep run follows a
documented exit-code contract, so a pipeline can branch on the kind of
failure — not just pass/fail:
| Code | Meaning |
|---|---|
0 | success |
1 | a task/step failed at execution time |
2 | usage error (e.g. no subcommand in a non-TTY pipeline) |
3 | the workflow could not be loaded/parsed (unknown key, version mismatch, missing file) |
4 | an assert step failed |
Each of these is verified against the binary. The payoff is a gate that reacts differently to a broken pipeline vs a genuine failure vs a failed assertion:
orchstep run check
case $? in
0) echo "ok" ;;
4) echo "an assertion failed — investigate the check" ;;
3) echo "the workflow itself is broken — fix the YAML" ;;
*) echo "a step failed" ;;
esacThat distinction matters. Exit 3 means a misconfigured pipeline (a typo, a
missing file, an engine-version mismatch) — it should fail fast and loud, and
it is a different problem from exit 1, a step that ran and returned an
error. Exit 4 isolates the case where your own assert caught a bad state, so
an alert can say "the deploy's health check failed" rather than "something
broke."
What you gained
| Need | Without it | With OrchStep |
|---|---|---|
| Parse the result | scrape stdout | --output json | jq |
| Logs and a result artifact | pick one | --json-file writes both |
| Branch on failure kind | only pass/fail | exit codes 1 / 3 / 4 |
| Right output for the context | configure per env | auto-detected TTY vs pipe |
The honest boundary
The JSON document is a run summary — workflow, task, per-step status and
captured output — not a firehose of every log line or timing trace. For deep
post-mortem (live variables, full context per step) that is what
orchstep context inspect and the dashboard are for. For CI gating and
artifacts, the JSON summary plus the exit code is exactly the right amount of
surface, and it is stable enough to script against.
Where to go next
- Output & Exit Codes — the full contract and JSON shape
- CLI Reference — every
runflag, including--json-file - OrchStep in CI — wiring it into GitHub Actions
Already running OrchStep in a pipeline? Add | jq -r '.status' to your run
step and gate on the exit code — two changes, and your CI speaks OrchStep
fluently.
curl -fsSL https://orchstep.dev/install.sh | sh