Environment & exit codes¶
Every variable this SDK reads or writes, and how an ocx exit code becomes a
Python exception. The composition itself happens in one place —
_env.build_spawn_env() — described conceptually in
Errors & credentials; this page
is the exhaustive wire-level table.
Neutralized on every spawn¶
Dropped from the ambient environment before anything else, regardless of
configuration — an inherited value here would silently retarget or
mis-scope a call. The drop is case-insensitive: ocx_no_verify goes
the same way OCX_NO_VERIFY does, because a Windows child resolves an
environment lookup case-insensitively and would read either spelling.
| Variable | Why |
|---|---|
OCX_PROJECT |
The SDK always targets a project through an explicit --project. |
OCX_GLOBAL |
Same reasoning — global scope is explicit, never ambient. |
OCX_QUIET |
The SDK controls output verbosity through its own presentation flags. |
OCX_NO_VERIFY |
A silent kill switch for signature verification — a skipped verification looks identical to a passed one. Say it at the call site instead: install(..., verify=False) / pull(..., verify=False), whose argv flag outranks the variable. |
OCX_NO_HOOK |
Governs the per-prompt shell hook, which a spawned child never renders. |
OCX_NO_COMPLETIONS |
Its sibling in the same ladder, for the same reason. |
OCX_SIGSTORE_TRUSTED_ROOT is cleared too, one step later — it is popped by
the config pass rather than the neutralization pass, because
OcxConfig.sigstore_trusted_root can put a value back. It redefines what
"trusted" means and outranks [trust.sigstore], so an inherited value could
repoint Fulcio, CT, and Rekor while every install still reported verified.
Written from OcxConfig¶
_env.build_spawn_env() maps OcxConfig fields onto these. A plain bool
field is set-only — False means "not requested", and the host's ambient
value (if any) survives. A field typed X | None can actively clear an
ambient value; None means "leave the host's value alone" — except for the
three rows marked always written, OCX_NO_UPDATE_CHECK, OCX_NO_CONSENT
and OCX_SIGSTORE_TRUSTED_ROOT, where saying nothing has to mean the SDK's
default rather than whatever the host exported.
Every value the SDK writes here — and every ambient value it clears — replaces the host's answer for that name in any case spelling, for the same reason the neutralization table gives. A field the SDK does not write leaves the ambient value exactly as the host spelled it.
| Variable | OcxConfig field |
Notes |
|---|---|---|
OCX_OFFLINE |
offline: bool |
Set to 1 when True; otherwise unwritten. |
OCX_FROZEN |
frozen: bool |
Same set-only shape. |
OCX_NO_CONFIG |
no_config: bool |
Same set-only shape. |
OCX_NO_UPDATE_CHECK |
no_update_check: bool |
Always written ("1" or "0") — its SDK default is True, so a caller asking for the update check back has to be able to beat an ambient OCX_NO_UPDATE_CHECK=1. |
OCX_NO_CONSENT |
consent: bool |
Always written ("1" unless consent=True). Without it every add/lock/pull/exec/update/init stamps state/projects/<key>/consent.json, which is what lets a shell prompt in that directory activate the project — a side effect a program step should not leave behind. ocx exec forwards it to nested ocx. This is the per-project stamp, not the shell-activation consent ocx shell allow records. |
OCX_HOME |
home: Path \| None |
|
OCX_CONFIG |
config: Path \| None |
|
OCX_INDEX |
index: Path \| None |
|
DOCKER_CONFIG |
docker_config: Path \| None |
Keep the directory 0700 — it holds registry credentials. |
OCX_JOBS |
jobs: int \| None |
|
OCX_MIRRORS |
mirrors: Mapping[str, str] \| None |
Serialized as JSON. |
OCX_MANAGED_CONFIG |
managed_config: str \| None |
MANAGED_CONFIG_DISABLED ("") force-disables an ambient managed-config tier; skipped entirely under no_config. |
OCX_NO_CONFIG_REFRESH |
no_config_refresh: bool \| None |
True writes 1; False explicitly clears an ambient value; None leaves it alone. |
OCX_INSECURE_REGISTRIES |
insecure_registries: Collection[str] \| None |
Fail-closed: any explicit value, including (), replaces the ambient set entirely rather than merging with it. |
OCX_SIGSTORE_TRUSTED_ROOT |
sigstore_trusted_root: str \| Path \| None |
Always written: a value sets it, None pops any ambient one. None therefore means ocx's own root of trust, not the host's — see the neutralization table for why. Typed str \| Path because CI configuration usually interpolates it as text. |
OCX_RECORDS_DIR |
records_dir: Path \| None |
Where exec writes its execution record; the directory must already exist. The per-call records_dir= on every exec/spawn verb overrides it. |
OCX_RECORDS_NAME |
records_name: str \| None |
The record's filename template ({time}, {host}, {pid}, {rand}). Per-call records_name= overrides it. |
OCX_TOOLCHAIN_DIR |
toolchain_dir: Path \| None |
Where the project toolchain links live. OCX_TOOLCHAIN_PINNED and OCX_TOOLCHAIN_ACTIVATE are deliberately not modelled — the former is the weakest tier under pinned= on env/exec, the latter is a shell-session concern. |
OCX_ANNOUNCE_TOKEN |
forge_token: str \| None |
The forge API credential announce/claim use — the top rung of the identity ladder. repr=False; redacted. |
OCX_ANNOUNCE_GIT_TOKEN |
forge_git_token: str \| None |
The push-leg credential for transport="git". repr=False; redacted. |
OCX_ANNOUNCE_GIT_USERNAME |
forge_git_username: str \| None |
The username the git push leg authenticates as. |
Auth — OCX_AUTH_<SLUG>_*¶
For every registry in OcxConfig.auth, the SDK writes (and first clears the
full existing triple for) the slug ocx's own registry_slug canonicalizes
to:
| Variable | Value |
|---|---|
OCX_AUTH_<SLUG>_TYPE |
basic or token |
OCX_AUTH_<SLUG>_USER |
Only for BasicAuth. |
OCX_AUTH_<SLUG>_TOKEN |
The password (BasicAuth) or bearer token (BearerAuth). |
<SLUG> is every character outside [A-Za-z0-9] in the registry name,
replaced with _, case preserved — ghcr.io becomes ghcr_io, so the
SDK writes OCX_AUTH_ghcr_io_TOKEN. ocx's own to_slug does not case-fold
and neither does this. An ambient OCX_AUTH_* for
a registry not named in config.auth passes through untouched; explicit
configuration only overrides its own slug. The clear is case-insensitive
across the whole name, so a configured ghcr.io also removes an ambient
ocx_auth_ghcr_io_* rather than shipping two credential sets for one
registry. Two registries that canonicalize to the same slug, or a registry
that canonicalizes to an empty slug, raise OcxError rather than silently
dropping or colliding credentials.
Credentials the host exported are redacted from logs and error text
alongside the ones the SDK wrote, in whatever case they were spelled. The
forge rungs — OCX_ANNOUNCE_TOKEN, OCX_ANNOUNCE_GIT_TOKEN, CI_JOB_TOKEN
— are redacted the same way, ambient or configured.
Propagation: ocx does not scrub non-forwarded variables from a spawned
child's environment, so a tool started through Project.exec or
package.exec inherits OCX_AUTH_*. See
Errors & credentials for the
credential-free pattern.
Bootstrap-only — OCX_INSTALL_*¶
Read by bootstrap.ensure, one rung below
its explicit keyword arguments and one above its own defaults. Never written
by the SDK.
This is the same OCX_INSTALL_* grammar
setup.ocx.sh defines, so an environment already
configured for the shell installer needs no code change here — export the
variable and bootstrap.ensure() picks it up. The two exceptions are called
out as No-op below, and the setup script's remaining variables
(OCX_INSTALL_NO_SETUP, OCX_INSTALL_NO_SMOKETEST, OCX_INSTALL_PRINT_PATH,
OCX_INSTALL_DOWNLOADER, OCX_NO_MODIFY_PATH) have no meaning here: this SDK
never runs setup, never prints, never modifies PATH, and has no
curl-or-wget choice to make.
| Variable | ensure() argument |
|---|---|
OCX_INSTALL_VERSION |
version — pins an exact release. Empty or unset takes the channel's latest. |
OCX_INSTALL_DIST_URL |
consulted by the default DistSource only — an explicitly constructed one does not honor it |
OCX_INSTALL_MIRROR_URL |
mirror_url — relocates the artifact host, as <mirror_url>/<tag>/<filename>. The manifest digest is still enforced, so a mirror moves bytes and never revalidates them; the manifest itself keeps coming from dist. Setting this makes the manifest sha256= mandatory — see the off-canonical rule. |
OCX_INSTALL_CA_BUNDLE |
ca_bundle — a PEM file trusted for the manifest and artifact downloads instead of the system store, for a TLS-intercepting proxy. Transport trust only: the manifest pin and the artifact digest are still enforced, so the bundle changes who may serve the bytes, never which bytes are accepted. |
OCX_INSTALL_REPO |
No-op. Listed for grammar parity with the setup script's OCX_INSTALL_* vars only — this SDK resolves artifact URLs from the manifest, never from a GitHub repository guess. |
OCX_INSTALL_FORCE |
no argument — forces a fresh download and install even when the cache already holds a correct binary |
OCX_INSTALL_QUIET |
No-op. Listed for grammar parity only — this module never prints, so there is nothing to quiet. |
Discovery¶
bootstrap.discover (used internally by Ocx() construction) resolves a
binary in this order:
- An explicit
exe=argument. OCX_SDK_EXE.PATH(the current working directory is excluded from the search on Windows).$OCX_HOME/…/current/content/bin/ocx— ocx's own stable install symlink.
Nothing found raises OcxNotFoundError, whose message names
bootstrap.ensure() as the fix.
Reserved for [env] entries¶
Project.env, Project.exec,
and PackageCommands.test accept extra
env= entries serialized as ocx's --env KEY[:TYPE[:SEP]]=VALUE flag. A key
in the OCX_* or __OCX_* namespace is rejected with OcxError — a project
cannot reconfigure how ocx itself resolves through this path. Set the
corresponding OcxConfig field instead.
Exit codes¶
The exit code of the ocx process is the error category — _process maps
it to a subclass, and nothing in the SDK ever classifies a failure by
matching stderr text.
| Code | ExitCode |
Exception | Retried by default? |
|---|---|---|---|
| 0 | OK |
— | — |
| 1 | FAILURE |
plain OcxProcessError |
no |
| 64 | USAGE |
UsageError |
no |
| 65 | DATA_ERR |
DataError |
no |
| 69 | UNAVAILABLE |
UnavailableError |
no — ocx classified it non-transient |
| 74 | IO_ERR |
IoError |
no |
| 75 | TEMP_FAIL |
TempFailError |
yes — the only retry signal (RetryPolicy.retry_on default) |
| 77 | NO_PERM |
PermissionDeniedError |
no |
| 78 | CONFIG |
ConfigError |
no |
| 79 | NOT_FOUND |
NotFoundError |
no |
| 80 | AUTH |
AuthError |
no — auth failures are never retried |
| 81 | POLICY_BLOCKED |
PolicyBlockedError |
no |
| 82 | DIRTY_RC_BLOCK |
DirtyRcBlockError |
no |
| 83 | TRANSPARENCY_LOG_UNAVAILABLE |
TransparencyLogUnavailableError |
no — retrying amplifies Rekor's rate limiting, and a later success is not restored trust |
| 84 | REFERRERS_UNSUPPORTED |
ReferrersUnsupportedError |
no |
| 85 | UNSUPPORTED_KEY_BACKEND |
UnsupportedKeyBackendError |
no |
| 86 | FORGE_CAPABILITY_UNAVAILABLE |
ForgeCapabilityUnavailableError |
no — the credential is valid and the forge was reached; an administrator has to enable job-token push on the target project or allowlist the publishing one |
| — (timeout, no exit code) | — | OcxTimeoutError |
no |
A process killed by a signal exits with a code ocx never assigns (137 for
SIGKILL, for instance); OcxProcessError.exit_code is a plain int for
exactly that reason, and such an exit lands as the generic OcxProcessError
rather than any per-code subclass. except OcxExecutionError is the catch
shape that covers both a non-zero exit and a timeout in one clause.
Two methods default their per-call retry to None regardless of session
policy: Ocx.login and
package.push — see
Concurrency & timeouts for why,
and pass retry= explicitly to override.