Running OpsChain in CI
Included from Basic tier
A CI job runs OpsChain on the local lane: the binary in the job, the
checked-out repository as the configured project, no server. This page
is the machine contract that lane offers a pipeline — the exit codes,
the run document, the quiet and non-terminal rendering rules, the
environment a step sees, the artifact download — and the usage of the
two published packagings of it, the GitHub Action and the GitLab CI
component. The guided walk is opschain learn 2.8; the flags
themselves are opschain docs cli/runs-create-change and
opschain docs cli/runs-create-workflow.
The invocation
opschain runs create change <action> --env <env> --local --approve-all -o json [--ref <git-ref>] [-i k=v ...]
opschain runs create workflow <code | file> --env <env> --local --approve-all -o json [--ref <git-ref>] [-i k=v ...]
| Flag | Role in a pipeline |
|---|---|
--local | Pins the lane. A runner that carries OPSCHAIN_SERVER or a stored context cannot redirect the job to a server. |
--approve-all | Answers every wait: step with its defaults. Without it an unattended run cancels (exit 3) the moment a wait step needs a terminal. |
-o json | Prints one run document on stdout at completion and nothing else; -o yaml is the same document in YAML. |
-q | Prints only the run id; the exit code is the verdict. |
--ref <git-ref> | Runs a clean snapshot of the named commit instead of the working copy; the document records it under run.commit. |
-i k=v | A typed input, validated against the action's or workflow's declarations before the run exists; repeat for lists. |
--timeout <d> / --step-timeout <d> | Bound the job; expiry exits 4. |
--asset <code> | Target an asset within the environment. |
The two lanes refuse each other's flags by name with exit 2, so a mis-targeted job fails loudly rather than half-working.
Exit codes
| Code | Meaning | Document flags |
|---|---|---|
| 0 | The run succeeded. | failed: false, cancelled: false, timed_out: false |
| 1 | The run failed (error, system_error, aborted), or the command itself failed before or after the run. | failed: true when a run exists |
| 2 | Usage error: bad flags, a lane refusal, a malformed k=v, a missing --env. | no document |
| 3 | The run was cancelled: a wait step with no interactive stdin and no --approve-all, or an interrupt (SIGINT). | cancelled: true |
| 4 | The run timed out. | timed_out: true |
The code is the only thing a pipeline needs to read to pass or fail a
job. A step that must keep going after a failure captures the code
(set +e … status=$? … set -e) and acts on it.
The run document
-o json prints exactly one document when the run reaches a terminal
status:
{
"run": {
"id": "01a1b365-bc34-731b-992e-469b83b00afa",
"kind": "change",
"action": "deploy",
"target": "myrepo/dev",
"status": "success",
"display_status": "success",
"commit": "eaa9ecfe14a700eb12dd773fb5e540e1281a90f5",
"git_rev": "main",
"created_by": "local",
"created_at": "2026-11-07T00:25:51Z",
"started_at": "2026-11-07T00:25:51Z",
"finished_at": "2026-11-07T00:25:51Z"
},
"steps": [
{"path": "deploy", "name": "deploy", "kind": "action", "status": "success", "children": [
{"path": "deploy/build", "name": "build", "kind": "action", "status": "success"},
{"path": "deploy/release", "name": "release", "kind": "action", "status": "success"}
]}
],
"failed": false,
"cancelled": false,
"timed_out": false
}
runis the run summary — the same itemopschain runs show -o jsonandruns list -o jsonprint;commit,git_rev,created_byandname(a workflow's display name) are omitted when empty,started_at/finished_atarenulluntil set, timestamps are RFC 3339 UTC.commitis the revision the run executed: the resolved SHA for a--refrun; for a working-copy run the checkout'sHEADSHA when the tree is clean, and<sha>+dirty.<digest>when the job had already written tracked or untracked files into it — the line that tells a reader whether the run proved the commit or the tree.stepsis the step tree in the stable step-view shape (opschain docs cli/runs-show):path,name,kind,status, withid,action,strategy,childrenomitted when empty, and on a workflow runnested_run_idnaming the change a step spawned.failed,cancelledandtimed_outcarry the exit-code verdict for a reader that only has the document.
The step logs are not in the document and are not on stdout under
-o json; read them back with opschain logs <run-id> --local
(--step <path> narrows it, --download <file> writes them to a file
for the job's own log store). The run is in the job's local history
(opschain runs list|show --local) for as long as the opschain home
lives.
Rendering rules without a terminal
| Output mode | stdout carries |
|---|---|
-o json / -o yaml | the run document only — no step tree, no transitions, no log lines |
-q | the run id only |
| default, stdout not a terminal | one line per step transition (<RFC 3339 time> <step path> <from> -> <to>) and each log line as [<step path>] <line>, in order; no redrawn tree, no colour |
| default, stdout a terminal | the live tree redrawn in place with log lines above it |
Stderr carries first-use notices (a generated master key, a local store
moved into place) and the error line of a failing command. A pipeline
that captures stdout to a file therefore gets a clean document; a
pipeline that wants a readable job log and the document runs the job
plain and reads opschain runs show <id> --local -o json afterwards.
A wait: step with stdin that is not an interactive terminal fails with
stdin is not an interactive terminal — re-run with --approve-all, or run from a terminal, the run cancels, and the exit code is 3.
The environment a step sees
A local run executes steps through the host executor; the job's environment does not pass through. A step receives:
- the host allowlist only:
HOME,TMPDIR,TMP,TEMP,LANG,LC_ALL,LC_CTYPE,TZ,USER,LOGNAMEand the proxy variables (HTTP_PROXY,HTTPS_PROXY,NO_PROXY, upper and lower case); PATH(the host's, with resolved tool shims and the engine's ownopschainshim ahead of it);- the engine-supplied variables —
OPSCHAIN_INPUT_<NAME>per input, the step-context and output files, the properties and events files (opschain docs runner-executors, "The execution environment").
A CI variable a step needs travels as an -i input or a property, not
as an exported variable. The 2026-11-11 single-node work brings a
named-variable passthrough and a secret-env:// reference to the local
lane; until then this allowlist is the whole story.
Secrets without a keychain
Secret inputs and secret-vault:// references resolve through the local
secret store at $OPSCHAIN_HOME/secrets/store.age, encrypted with an age
master key (opschain docs cli/secret). The key resolves in order:
OPSCHAIN_MASTER_KEY— the identity (AGE-SECRET-KEY-1…) as a masked CI secret exported into the job;- the key file
$OPSCHAIN_HOME/secrets/master.key, mode0600(looser permissions are refused); - the OS keychain — skipped entirely with the global
--no-keychain, which a headless runner should always pass.
Seed the store on stdin, never on the command line:
printf '%s\n' "$DB_PASSWORD" | opschain secret set db/password --no-keychain
Point OPSCHAIN_HOME (or --home) at a directory inside the job
workspace so the store, the local keyset and the run history are the
job's alone and do not outlive it on a shared runner.
Images are not pulled
The local lane is offline-first: a step whose image: is not present on
the host fails by name (image <ref> is not present locally and image pulling is disabled). There is no pull verb; pull in a job step before
the run and let the CI system's layer cache do the rest.
Artifacts
opschain runs artifacts <run-id> --local
opschain runs artifacts <run-id> --local --download <name> [--out <dir>]
Lists a run's artifacts newest-first, or downloads one by name: a
collected step artifact (application/x-tar) unpacks under --out with
its workspace-relative paths; any other artifact lands as one file. On
the local lane a workflow run's listing includes the artifacts of every
change it spawned. -o json on the listing prints the summaries
(id, code, run_id, size, content_type, file_count,
created_by, created_at) for a loop that downloads them all by code (opschain docs cli/runs-artifacts,
opschain docs artifacts).
The GitHub Action
LimePoint/opschain-action is a composite action that installs the
pinned release for the runner's platform, verifies its digest, runs the
invocation above, writes the run document to an output and to the job
summary, uploads the artifacts, and sets the step result from the exit
code:
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: LimePoint/opschain-action@v1
id: run
with:
version: v5.7.0
action: deploy
env: dev
inputs: |
release=${{ github.ref_name }}
dry_run=false
ref: ${{ github.sha }}
download-token: ${{ secrets.OPSCHAIN_RELEASES_TOKEN }}
env:
OPSCHAIN_MASTER_KEY: ${{ secrets.OPSCHAIN_MASTER_KEY }}
- run: echo "run ${{ steps.run.outputs.run-id }} finished ${{ steps.run.outputs.status }}"
| Input | Default | Meaning |
|---|---|---|
version | required | The release to install (v5.7.0). |
download-token | — | A GitHub token with read access to the releases repository; releases are invitation-only, so without it the install fails by name. Store it as a repository secret. |
action | — | The action to run; exactly one of action and workflow. |
workflow | — | The workflow code or file to run instead of an action. |
env | required | The target environment code. |
asset | — | The target asset code. |
inputs | — | Typed inputs, one key=value per line. |
ref | — | A git ref to run a clean snapshot of (${{ github.sha }} pins the job's commit). |
approve-all | true | Answer wait steps with their defaults. false makes a wait step fail the job with exit 3. |
artifacts | true | Download the run's artifacts and upload them with actions/upload-artifact. |
artifact-name | opschain-artifacts | The name of the uploaded artifact bundle. |
home | ${{ runner.temp }}/opschain-home | The opschain home for the job. |
working-directory | . | The repository directory the run executes in. |
timeout | — | A run timeout, e.g. 30m. |
Outputs: run-id, status, commit, exit-code, run-document (the
JSON, single line) and artifacts-dir. The step fails when the exit
code is not 0; a job that wants to read the outputs first sets
continue-on-error: true on the step and checks exit-code.
The GitLab CI component
LimePoint/opschain-gitlab-ci publishes two templates with the same
inputs, run-change and run-workflow:
include:
- component: gitlab.com/LimePoint/opschain-gitlab-ci/run-change@1.0.0
inputs:
version: v5.7.0
action: deploy
env: dev
inputs: |
release=$CI_COMMIT_REF_NAME
dry_run=false
ref: $CI_COMMIT_SHA
stage: test
The job installs and verifies the binary, runs the invocation, keeps
opschain-run/run.json and opschain-run/artifacts/ as job artifacts
(always, so a failed run's report is kept too), exports OPSCHAIN_RUN_ID,
OPSCHAIN_RUN_STATUS and OPSCHAIN_RUN_COMMIT in a dotenv report for
later stages, and fails the job on a non-zero exit code. Set
OPSCHAIN_MASTER_KEY as a masked, protected CI/CD variable for the
secret store.
What a CI run cannot do
A CI run is a local run, so everything the local lane refuses by name is refused in CI. The pairs that come up in pipelines:
| Refused in CI | Server lane |
|---|---|
change: steps spawning a child change on another node | the change runs against a real node (opschain learn 3.1) |
content() / list() over the repository | a server change resolves them from its git remote |
packs declared with url: / remote: | the server fetches and pins the pack; the bundled pack and bundle: files work in CI |
workflow retry (runs retry on a workflow run) | retry on the server, or re-run the job |
| a run anyone can cite as evidence | raise the change on a server from the job — the LimePoint/opschain-action reusable workflows opschain-change.yml / opschain-workflow.yml (opschain docs integrations-github, "The published workflows"), or opschain login --server <url> --token <api key> followed by runs create change <node> --action <a> --follow, whose exit codes are the table above |
Related pages
opschain docs cli/runs-create-change/opschain docs cli/runs-create-workflow— every flag and named error of the invocation.opschain docs cli/runs-artifactsandopschain docs artifacts— the artifact store and the download.opschain docs cli/secret— the local secret store and its master key.opschain docs runner-executors— the host executor's environment and the offline-first image rule.opschain docs cli/init— scaffolding.opschain/in a repository.opschain learn 2.8— the guided walk;opschain learn 0.2— the two lanes.