Skip to main content
Version: v5.6.1

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
  • name is the stored artifact code: lowercase letters, numbers and underscores, at most 100 characters, unique within the step.
  • path is 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: true fails the step when collection fails — no matching files, an over-budget set, or a storage failure — as an ordinary step failure (never system_error), so on_error: handling still applies. With the default required: false, a collection failure leaves a warn:artifacts:collection event 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 (--local runs): 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 (--local and 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 under name: a collected artifact is unpacked under --out with its workspace-relative paths; a raw (DSL-stored) artifact is written as a single file.
  • The v2 API serves the same reads: NodeArtifactService.ListNodeArtifacts scoped by change_id, GetArtifact, and GetArtifactDownloadTarget for the content URL. Collected artifacts carry file_count.
  • opschain artifacts list|get|put remain 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.

  • opschain docs storage — the blobstore the archives land in.
  • opschain docs retention — the artifacts_days class.
  • opschain docs config — the artifacts.* budget keys.
  • opschain docs events — api:artifacts:create and warn:artifacts:collection.
  • opschain docs orchestration — the action-file step schema.