---
title: "Output contract"
url: "https://docs.alisx.com/reference/cli/output-contract/"
---
# Output contract

The exact shape of what `alis` writes to stdout and stderr under `--json`, and the fields of an operation result. For scripts and coding agents that parse the CLI.

## Streams

| Stream | Carries | Format |
|---|---|---|
| stdout | Exactly one machine-readable result: the command's result object, the final operation, or an error envelope. | One JSON document. |
| stderr | Progress while a long-running command waits. Never results. | NDJSON: one small JSON object per line. |
| exit code | Success or the class of failure. | See [Exit codes](https://docs.alisx.com/reference/cli/exit-codes.md). |

Without `--json`, stdout is a human summary and stderr carries readable progress.

## Invocation rules

- Run one standalone command. Pass the working directory with the tool's workdir or `alis --cwd /absolute/path <command>`; no wrappers, chains, pipes or redirects.
- Read stdout whole. A success result for a streamed operation is under 1 KB; never `tail`, `grep` or `2>/dev/null` it.
- stdout is the completion signal. There is no final stderr event.
- Interrupts (Ctrl-C, SIGTERM) never cancel the server-side operation; re-attach with `alis operations wait <name> --json`.

## Progress events (stderr)

One event per new progress line:

```json
{"elapsed":"1:42","operation":"operations/<id>","progress":"Building image neurons/orders-v1"}
```

A timeout or interrupt ends the stream with a warning event that names the resume command:

```json
{"elapsed":"10:00","warning":"…","next":"alis operations wait operations/<id> --json"}
```

`--quiet` suppresses progress. `--verbose` streams full progress snapshots instead of pruned lines.

## Operation results (stdout)

`define`, `build`, `deploy`, `blocks install|upgrade|publish|uninstall`, `product new` and `operations wait` print the final operation as JSON, also when it failed. Always parse stdout before falling back to stderr.

### Common fields

Start (`--async`), `wait` and `describe` share these top-level fields:

| Field | Type | Meaning |
|---|---|---|
| `schemaVersion` | number | `1`. |
| `name` | string | `operations/<id>`. The handle for `operations wait|describe|cancel` and `logs build|deploy`. |
| `done` | boolean | Explicit; `false` while running. |
| `status` | string | `running`, `succeeded` or `failed`. |
| `version` | string | The service version the operation produced or deployed, when known. |
| `notes` | string | A one-line summary of what the operation is doing. |
| `logsUri` | string | Link to the execution log, when available. |
| `error` | object | Present when the operation failed. |

A start result also retains a legacy `metadata` object; read the common fields at the top level. Absent fields mean zero or empty.

### Pruning

Streamed results are pruned to what is actionable:

- completed build steps and applied Terraform resources are dropped;
- failed and running steps, `diagnostics`, `failure` (the last 60 `errorLines`) and define `errorDetails` (the first 40 lines) remain;
- `images[]` lists each built image by `path`; a failure appears under `images[].progress.failure`;
- `deployments[]` lists each environment deployment with `name`, `state`, `logsUri` and `progress` (for example `changeSummary` with the Terraform `operation` and `change` count); a failure appears under `deployments[].progress.failure`;
- `buildLogsUri` and `logsUri` link to the build and deploy execution logs.

`--verbose` keeps every field, including zero values.

### Example: a finished build with deploy

```json
{
  "schemaVersion": 1,
  "name": "operations/<id>",
  "done": true,
  "status": "succeeded",
  "version": "1.24.3",
  "notes": "Deploying infrastructure to 1 environment",
  "buildLogsUri": "https://…",
  "logsUri": "https://…",
  "images": [{"path": "."}, {"path": "worker"}],
  "deployments": [
    {
      "name": "organisations/acme/products/shop/environments/<env-id>/deployments/orders-v1",
      "state": "RUNNING",
      "logsUri": "https://…",
      "progress": {"changeSummary": {"operation": "apply", "change": 5}}
    }
  ]
}
```

### Async start

`--async` returns as soon as the operation exists. `next` is the exact wait command, including `--cwd` when one was given:

```json
{
  "schemaVersion": 1,
  "name": "operations/<id>",
  "done": false,
  "status": "running",
  "next": "alis operations wait operations/<id> --json"
}
```

### Active list rows

`alis operations list --json` returns `operations[]`, each with `name`, `type` (`define`, `build`, `deploy`), `target` (package ID), `startedAt` and `next`. With `--active`, rows that could not be read keep `status: "unknown"` and carry `inspectionError`; an unreadable operation is not evidence that it stopped.

## Error envelope

Pre-flight failures and refusals print one envelope on stdout:

```json
{"error": {"code": "…", "message": "…", "retry": "…", "retryArgv": ["…"], "retryCwd": "…", "agent": "…", "details": {}}}
```

Fields and codes: [Exit codes](https://docs.alisx.com/reference/cli/exit-codes.md).

## Creation results

Creation commands include identifiers in their result; verify them with exit code 0. An envelope for something that already exists (`product_exists`, `service_exists`) carries an `agent` field describing what to do instead of repeating the mutation.

## Dry runs

`alis define --dry-run --json` reports `valid`, `diagnostics[]` (severity, file, path, line, column, message) and `git` (uncommitted files, unpushed commits, the commit a define would use). Offline, only local checks run and the envelope carries them under `error.details` with `compiled: false`.

`alis run --dry-run --json` prints the plan as JSON without starting anything (`alis preview --json` starts nothing to begin with). `alis specialist send-message --dry-run --json` prints the prepared envelope and sends nothing.

Related: [Script the CLI with JSON](https://docs.alisx.com/how-to/cli/script-the-cli-with-json.md), [Run long operations asynchronously](https://docs.alisx.com/how-to/cli/run-long-operations-asynchronously.md).
