- integrations
- Python
- Guides
- Reproduce a toolchain in CI
Reproduce a toolchain in CI
Make a CI job install a pinned ocx, pull the toolchain that ocx.lock pins,
and fail when the lock no longer matches ocx.toml. This page follows the
reproducible-CI use case in the
docs use-case note.
You need a project with a committed ocx.toml and ocx.lock, and a runner
with network access.
Pin the binary in the workflow
Section titled “Pin the binary in the workflow”If a job only needs the tools on PATH, skip Python. The SDK’s own
CI workflow
uses the setup-ocx action, which installs the pinned binary and activates
the project:
- uses: ocx-sh/setup-ocx@25fa771f8572572dc64528db89560de68a163a0e # v1 with: version: "0.6.2"- name: Verify run: task verifyPin the action by full commit SHA, as above.
Pin the binary from Python
Section titled “Pin the binary from Python”When a Python step drives the toolchain, let the SDK provision the binary.
bootstrap.ensure() downloads the
release you name, verifies it and caches it:
import tempfilefrom pathlib import Path
from ocx_sdk import Ocx, bootstrap
ocx = Ocx(exe=bootstrap.ensure(version="0.6.2"))
root = Path(tempfile.mkdtemp())(root / "ocx.toml").write_text('[tools]\ntask = "ocx.sh/go-task/task:3"\n')project = ocx.project(root)project.lock()Name an exact version=. The default follows the stable channel, so
two runs a week apart can differ.
Fail the build on drift
Section titled “Fail the build on drift”lock(check_only=True) verifies the lock and writes nothing. It returns
None when the lock is current:
assert project.lock(check_only=True) is NoneWhen ocx.toml has changed since the last ocx lock, it raises
DataError. A missing lock raises
ConfigError. Both are
non-retryable, so let them fail the job:
from ocx_sdk import DataError
(root / "ocx.toml").write_text('[tools]\ntask = "ocx.sh/go-task/task:3"\nuv = "ocx.sh/astral-sh/uv:0"\n')try: project.lock(check_only=True)except DataError as error: print(error.exit_code)else: raise AssertionError("expected drift")Then pull and run:
(root / "ocx.toml").write_text('[tools]\ntask = "ocx.sh/go-task/task:3"\n')project.pull()project.exec(["task", "--version"])Harden the job
Section titled “Harden the job”A CI runner’s ambient environment chooses which binary runs and which
credentials attach to registry calls. Pass exe=, a minimal HostEnv and an
empty insecure_registries to take those choices away from it. See
Hermetic CI for every lever, and
Vendoring a dist.json for air-gapped mirrors.
See also
Section titled “See also”- Bootstrap: pinning, channels and mirrors.
- Fix an error: what
DataErrorandConfigErrormean.