CLI

Use the CLI in scripts

Parse JSON, check remote exit codes, and recover operations and asynchronous execution jobs.

Prefer JSON for ordinary command results

For ordinary, non-streaming commands, use --json or --output json. The common success envelope is written to stdout:

{
  "ok": true,
  "command": "project exec",
  "data": {
    "stdout": "hello\n",
    "stderr": "",
    "exit_code": 0
  },
  "meta": {}
}

This is an abbreviated example: actual data and metadata depend on the command. Caught command errors normally produce a nonzero CLI exit status and an ok: false envelope on stderr, with error.code and error.message. Some errors also include error.details or error.hint.

Keep stdout and stderr separate. Progress, diagnostics, and approval messages can also appear on stderr, so do not assume the entire stderr stream is always one JSON document. Argument-parser and startup failures can have a different format from command-handler errors.

The common renderer implements JSON and human-readable output. Although --output advertises YAML, do not rely on YAML output across commands. --quiet suppresses human-formatted success output, not JSON results.

Streaming, passthrough, and declaration-printing commands have their own output formats. In particular, project codex exec --stream --json can emit stream messages on stdout before its final envelope. Do not use the single-document parsing pattern below with --stream or --jsonl.

Check the requested work, not just the envelope

Command Additional result check
auth status --check data.check.ok is true.
project exec in JSON mode data.exit_code is 0.
Asynchronous project exec data.status is completed and data.exit_code is 0.
op wait data.status is succeeded.

JSON-mode project exec can exit locally with status zero and return ok: true even when the process inside the project fails. Its output and exit code are returned inside data.

The following Bash example requires jq. Set CLI_PROFILE to the saved profile from the quickstart, and PROJECT_ID to the full ID from project list. It runs pwd in that project.

: "${CLI_PROFILE:?Set CLI_PROFILE to your saved profile name}"
: "${PROJECT_ID:?Set PROJECT_ID to the project ID}"

result=$(
  cocalc --profile "$CLI_PROFILE" --json \
    project exec --project "$PROJECT_ID" -- pwd
) || exit "$?"

if ! printf '%s\n' "$result" |
  jq -se '
    length == 1 and
    (.[0] | .ok == true and .data.exit_code == 0 and
     (.data.stdout | type == "string"))
  ' >/dev/null
then
  printf '%s\n' "$result" >&2
  exit 1
fi

printf '%s\n' "$result" | jq -j '.data.stdout'

Retain operation IDs and check terminal status

Commands that submit a long-running operation can return an op_id. Retain it so a later command can inspect the same operation.

Set OP_ID to the returned ID:

cocalc --profile "$CLI_PROFILE" --json op get "$OP_ID"
cocalc --profile "$CLI_PROFILE" --json --timeout 10m --poll-ms 2s op wait "$OP_ID"

op wait finishes at succeeded, failed, canceled, or expired. It can return ok: true for any of these terminal states. In a script, require both .ok == true and .data.status == "succeeded".

A wait timeout does not cancel the operation. Inspect the retained ID before submitting another mutation; otherwise you can duplicate work that is still running. To request cancellation deliberately:

cocalc --profile "$CLI_PROFILE" --json op cancel "$OP_ID"
cocalc --profile "$CLI_PROFILE" --json op get "$OP_ID"

For op wait, global --timeout controls the wait budget, --rpc-timeout controls individual requests, and --poll-ms controls polling. Their defaults are 600 seconds, 30 seconds, and one second. These are not a universal hard wall-clock deadline for every CLI command.

Project execution uses a different job ID

Start a shell command asynchronously:

cocalc --profile "$CLI_PROFILE" --json \
  project exec --project "$PROJECT_ID" --async --timeout 120 -- pwd

Retain the returned data.job_id as JOB_ID, then inspect or wait for it:

cocalc --profile "$CLI_PROFILE" --json \
  project exec --project "$PROJECT_ID" --job-id "$JOB_ID"

cocalc --profile "$CLI_PROFILE" --json --timeout 10m \
  project exec --project "$PROJECT_ID" --job-id "$JOB_ID" --wait

A job_id is not an op_id; do not pass it to op wait. Job status and results are kept in memory, with a bounded cache for completed results. Persist important outputs in your workflow and do not assume a job can be recovered after a service restart or cache expiry.

Place global wait options before project. The subcommand's project exec --timeout 120 is a remote command limit in seconds. Check command-specific help before copying timeout options to another workflow.