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.