Step artifacts
Included from Basic tier
Step artifacts are a run step's declared products, collected from the run
workspace at the step's completion, before any per-step cleanup or
run-end workspace sweep — cancelled and failed runs included — and stored
past the workspace's deletion. They complement step outputs: outputs
are control-plane metadata under a hard 64 KiB budget; artifacts are the
transport for anything larger (reports, dist trees, bundles). Collected
artifacts land in the same artifact store the OpsChain.artefact DSL and
opschain artifacts commands use, so one retention policy, one audit event
and one read surface cover both.
Declaring artifacts
A run: step in an action file may carry an artifacts: list:
steps:
- name: publish_report
run: make report
artifacts:
- name: integration_report # the artifact code it is stored under
path: integration-report/** # glob, relative to the run workspace
required: true # default false
nameis the stored artifact code: lowercase letters, numbers and underscores, at most 100 characters, unique within the step.pathis a doublestar glob resolved against the run workspace root:*stays within a path segment,**crosses segments. Absolute paths and..segments are refused when the file loads. Only regular files are collected (symlinks and directories are skipped).required: truefails the step when collection fails — no matching files, an over-budget set, or a storage failure — as an ordinary step failure (neversystem_error), soon_error:handling still applies. With the defaultrequired: false, a collection failure leaves awarn:artifacts:collectionevent on the step and the run continues.
A step that already failed (or was cancelled) is never re-failed over an artifact: collection still runs best-effort — a report produced before the failing assertion is usually exactly what you want to keep — and any failure is recorded as the warning event.
What is stored
Each declaration's matched files are packed as one deterministic tar
archive (sorted workspace-relative paths, fixed epoch timestamps, 0644 or
0755 modes), so the same file set digests identically on every lane. The
record carries the run, step, name, file count, total size, blob digest
and content type (application/x-tar); storing it emits the
api:artifacts:create audit event.
- Local lane (
--localruns): the archive is stored in the local blob store with its artefacts row, and the files are additionally unpacked under<opschain home>/artifacts/<run-id>/<name>/…for direct use. - Server lane (runner-executed steps): the runner uploads the archive
over the runner protocol (
DataService.PutArtefact, protocol version 5) and the server stores it in the blobstore (blob_root). A runner whose protocol predates step artifacts never receives the declarations — the server withholds them loudly with a system line in the step's log.
Size budgets
Two budgets bound collection, both configured in config.yaml's
artifacts: block (opschain docs config): artifacts.max_artifact_mb
(default 512) meters one artifact's matched file bytes, and
artifacts.max_run_mb (default 1024) meters a run's collected artifacts
cumulatively (the server re-enforces both on the stored bytes at upload).
An over-budget collection is refused with the budget named, under the
required/optional semantics above. The local lane reads the same
config.yaml when the home carries one, else the compiled defaults apply.
Reading artifacts back
opschain runs artifacts <run-id>lists a run's artifacts newest-first (--localand against a server); on the local lane a workflow run lists the artifacts of every change it spawned.opschain runs artifacts <run-id> --download <name> [--out <dir>]fetches the newest artifact stored undername: a collected artifact is unpacked under--outwith its workspace-relative paths; a raw (DSL-stored) artifact is written as a single file.- The v2 API serves the same reads:
NodeArtifactService.ListNodeArtifactsscoped bychange_id,GetArtifact, andGetArtifactDownloadTargetfor the content URL. Collected artifacts carryfile_count. opschain artifacts list|get|putremain the node-scoped surface.
Retention
Collected step artifacts are artefact rows, so the existing
retention.artifacts_days class covers them: expired artifacts are
exported to the retention archive, tombstoned (downloads answer the purged
error) and their blob bytes released once unreferenced — see
opschain docs retention. The card asked for a
retention.artifacts_days key on the existing machinery; that key already
existed for the artifact store, and collected step artifacts deliberately
join it rather than adding a parallel class. The local lane's unpacked
convenience copies under <home>/artifacts/ are plain files on the
developer's machine and are not swept by server retention.
⚠️ Artifacts bypass the log masker
Step logs and outputs pass through the run's secret masker; collected
artifact files do not — they are copied byte-for-byte from the
workspace. A declared path must never select files carrying rendered
secret values (a materialised properties file, a rendered credential, a
vault export). Treat an artifact declaration as a publication decision:
anyone who can read the run can download its artifacts. The release lane's
release-artifacts/<version>/ tree (whose build-info.json carries the
garble seed) is deliberately not declared for exactly this reason —
custody material is staged to the operator-controlled absolute
release.custody_dir path and moves through the vault runbook instead.
Related pages
opschain docs storage— the blobstore the archives land in.opschain docs retention— theartifacts_daysclass.opschain docs config— theartifacts.*budget keys.opschain docs events—api:artifacts:createandwarn:artifacts:collection.opschain docs orchestration— the action-file step schema.