Environment
Primer
Truthy Values
Some OCX options can be configured using environment variables instead of command line flags. For example, the --offline flag can be set by configuring the OCX_OFFLINE environment variable to a truthy value. Truthy values are case-insensitive and include:
1yyesontrue
for enabling an option, and
0nnoofffalse
for disabling an option.
Internal
Presentation flags do not propagate
The presentation flags --log-level, --format, and --color are CLI-only by design — they have no OCX_* counterpart and never propagate from a parent ocx into a subprocess (such as a generated entrypoint launcher). Carrying them through env would leak ocx's own logging, JSON output, or ANSI color choices into the launcher's child stream. Only resolution-affecting policy (binary path, offline, remote, config file, index) propagates.
Shell Activation Files
The ocx self setup command writes one thin shim file per supported shell family into $OCX_HOME. Each shim calls ocx self activate at shell start, which delegates runtime logic to the binary: PATH prepend, completion injection, and global toolchain env eval. Shim files are byte-identical across users — no install-time substitution occurs.
| File | Shell | Mechanism | Wired by |
|---|---|---|---|
$OCX_HOME/env.sh | bash, zsh, dash, ash, ksh | existence-guarded . "${OCX_HOME:-$HOME/.ocx}/env.sh" block-marker | ocx self setup writes block-marker to ~/.bash_profile, ~/.zprofile, or ~/.profile |
$OCX_HOME/env.fish | fish | source "$OCX_HOME/env.fish" from conf.d | ocx self setup writes ~/.config/fish/conf.d/ocx.fish |
$OCX_HOME/env.nu | nushell | Nushell autoload via vendor/autoload/ | ocx self setup writes ~/.local/share/nushell/vendor/autoload/ocx.nu (auto-sourced) |
$OCX_HOME/env.elv | elvish | existence-guarded eval (slurp < …/env.elv) block-marker | ocx self setup writes block-marker to ~/.config/elvish/rc.elv |
$OCX_HOME/env.ps1 | PowerShell | existence-guarded . …/env.ps1 block-marker in $PROFILE | ocx self setup writes block-marker to $PROFILE |
Each shim resolves OCX_HOME at runtime using shell-native assign-if-unset syntax (e.g. : "${OCX_HOME:=$HOME/.ocx}"), so activation works in fresh shells where OCX_HOME has not been exported. The managed block in your profile resolves the same ${OCX_HOME:-$HOME/.ocx} fallback (the block sources env.sh, which is what exports OCX_HOME, so it cannot depend on it) and existence-guards the source, so a login shell with OCX_HOME unset never errors on a missing file.
These files are ocx-managed — customize your own RC
The env.* shims and the dedicated fish/nushell files (~/.config/fish/conf.d/ocx.fish, ~/.local/share/nushell/vendor/autoload/ocx.nu) are owned and regenerated by ocx self setup, and refreshed by ocx self update. They are rewritten to canonical content whenever they drift, without a --force prompt — the no-clobber-without---force guarantee applies only to the managed block inside your own shell profiles (~/.zshrc, ~/.bash_profile, $PROFILE, …). Put personal shell customization in those RC files, not in the ocx-managed files.
_OCX_APPLIED
REMOVED — this specific variable (single underscore) is not read or written by current OCX. Do not set or reference
_OCX_APPLIEDin shell profiles.Global toolchain activation is handled by
$OCX_HOME/env.sh, sourced from the login profile via a block-marker idempotent line written by the installer. The file runseval "$(ocx --global env --shell=sh)". Project toolchain activation is handled by a per-prompt reconciler — a separate mechanism from the one_OCX_APPLIEDbelonged to, carrying its own private state variable,__OCX_ENV_STATE. See Shell Integration for how it decides what belongs onPATHat every prompt.ocx direnv exportremains available as an alternative, stateless backend for existing direnv users.
__OCX_ENV_STATE
The private carrier the per-prompt reconciler uses to remember what it last applied to this shell's environment — the ledger that lets it tell "a value ocx wrote" apart from "a value you typed" when deciding what to change at the next prompt. See Shell Integration → The state carrier for the full contract (encoding, size cap, degradation on corruption).
You will not normally read or write this variable by hand. Two things about it are useful to know:
- It is a deliberate exception to the private
__OCX_*convention. Every other__OCX_*variable is process-internal and never meant to be seen. This one has to cross the process boundary into your interactive shell — a child process cannot mutate its parent's environment any other way — and the moment a documented repair gesture exists for it (below), it is a user-facing contract whether or not the name looks internal. unset __OCX_ENV_STATEis the repair gesture for a shell whose state has gone stale or whose carrier looks corrupted. See Shell Integration → Repairing a stuck shell for what it costs — clearing the ledger also clears the memory of what the reconciler is allowed to restore.
is_reserved_ocx_key already refuses this name everywhere ordinary env values are accepted — a package cannot declare it, --env cannot set it, and ocx exec strips it from anything it would otherwise forward unchanged.
__OCX_ENV_PWD
The one private variable the elvish per-prompt hook keeps in the environment, elvish only. Every other shell holds the equivalent fact in a shell variable; elvish's activation stream reaches the shell through eval, whose assignments cannot be relied on to escape the evaluated unit, so this one rides the environment instead.
__OCX_ENV_PWD holds the recording shell's pid, its $pwd, and the three direnv/mise yield sentinels, space-joined (<pid> <pwd> <DIRENV_DIR> <MISE_SHELL> <__MISE_ORIG_PATH>, an unset sentinel contributing an empty field) — every input elvish's per-prompt guard can evaluate without spawning a process, folded into one recorded string. The name is narrower than the contents: the value answers "has anything the guard can see moved?", of which the directory is one term.
The pid term matters: a bare $pwd would let a child elvish process inherit an already-matching value from its parent's environment and read its own first prompt as already reconciled, even though it has never run the hook itself. Folding the pid in makes an inherited value a mismatch by construction, so a child's first prompt reconciles like every other shell's does.
The sentinel tail is there because elvish has no shell-local that a hook's eval unit can both write and read — every other shell keeps the equivalent fact in a shell variable — so the sentinels ride the one store elvish does have rather than costing a second exported variable. A direnv or mise takeover therefore changes the recorded value and reconciles at the next prompt, exactly as a cd does. Elvish's guard compares the whole recorded string against its own live pid, $pwd and sentinel values; the ocx wrapper clears it (to the empty string) so the next prompt reconciles unconditionally.
Whether this shell has already registered its hook is decided a different way, and does not use a second environment variable: it is read off the registered $edit:before-readline closure's own source, scanned for a sentinel comment. That is why a shell that replaces its own process image with exec elvish still registers correctly — the closure list a new process image starts with is always empty, regardless of what the old image's environment held.
__OCX_ENV_PWD is not a repair gesture and is not worth setting by hand — unset-env __OCX_ENV_STATE is the one documented way to reset a stuck shell. It sits inside the reserved __OCX_* namespace, so no package can declare it and --env cannot set it.
OCX_ALLOW_YANKED
When set to a truthy value, opts in to resolving a tag that index.ocx.sh has marked yanked. Without it, a tag resolve against a yanked entry is refused — a yank is a publisher signal, not a delete, so OCX treats it as a hard stop by default rather than silently installing withdrawn content.
export OCX_ALLOW_YANKED=1The gate applies identically online and offline: a live index.ocx.sh fetch and a tag resolve against a committed or shipped local copy of it — with zero network — both read the same yanked field from the root document and, without this variable, both refuse with exit code 65. Populating a local index by ocx index update or shipping one inside a devcontainer feature does not bypass the check; a yanked entry stays refused until this variable is set or the reference is pinned by digest.
Only affects resolution through the public index — a digest-pinned reference already identifies exact content and never needs this variable, yanked or not. The bypass is digest-pin-only, stricter than an ecosystem that treats an exact version pin as sufficient rather than a content digest: ocx.lock always stores digests, so a project resolved from a lock is unaffected by a yank either way.
This is resolution-affecting: it is forwarded to every subprocess ocx spawns via apply_ocx_config, so child invocations — generated launchers, nested ocx exec calls — honor the same opt-in.
OCX_ANNOUNCE_GIT_TOKEN
The push half of the forge credential pair, used only under --transport git. It authenticates every git invocation that talks to the index remote — the blobless fetch and the push that carries the branch and the merge-request options — while the REST reads keep using OCX_ANNOUNCE_TOKEN. Set it when the identity allowed to write the repository is not the identity allowed to call the API: a deploy token for the write, a personal token for the reads.
Leave it unset and the write reuses the resolved API credential. Set it to an empty string and it is treated as unset, so a wrapper that exports OCX_ANNOUNCE_GIT_TOKEN= cannot silently turn an authenticated write into one that authenticates as nobody.
The secret travels as the password half of an HTTP Basic Authorization header injected through git's own configuration environment — never in a URL, never in argv, never in .git/config. The user half is OCX_ANNOUNCE_GIT_USERNAME.
Never forwarded to child processes. OCX reads this variable directly and strips it from the environment of every subprocess it spawns, plugins (ocx-<name>) included.
The pair is split on purpose
OCX_ANNOUNCE_TOKEN is not stripped — a plugin that announces from its own process inherits it deliberately. So a plugin inherits the API half and not the write half. A plugin that needs to write over the git transport must be handed this variable explicitly rather than relying on inheritance.
OCX_ANNOUNCE_GIT_USERNAME
The user half of the HTTP Basic pair the git write transport presents, defaulting to gitlab-ci-token. GitLab authenticates a job token under that conventional user and ignores which non-empty user carries a personal or project token, so the default is right unless a self-hosted instance says otherwise.
A value containing :, and an empty one, are ignored — the default is used instead, and nothing fails. HTTP Basic has no escaping on the pair and a server splits the decoded text on the first colon, so a:b would re-partition it into user a and secret b:<secret>: a 401 that reads like a bad token. Setting a username the instance does not expect therefore fails at the push, not at the command line.
This is not a credential — holding it authenticates nobody — so it is not stripped from the environment ocx spawns a plugin (ocx-<name>) with, while its secret sibling OCX_ANNOUNCE_GIT_TOKEN is. Note that ocx never sets it on a child either: a plugin inherits it from the ambient environment, the way it inherits anything ocx does not scrub.
OCX_ANNOUNCE_TOKEN
A forge personal access token, read by ocx package announce and by ocx package claim when they open or update a pull or merge request against the index repository — from a fork with --fork, or from a branch on the index repository itself when --fork is omitted. Writing locally with --out does not need one.
One variable serves both forges; which kind of token to put in it follows the forge the run targets:
| Index forge | Token | Required scope |
|---|---|---|
| GitHub, GitHub Enterprise Server | Personal access token | repo (or fine-grained: read + pull-request write on the index and the fork) |
| GitLab, self-managed GitLab | Personal, project or group access token | api |
What this variable has to carry depends on the write transport:
| Transport | This variable authenticates | Credential for the repository write |
|---|---|---|
--transport api (default) | Every REST call: reading the committed entry, creating the branch and the commit, opening the request | none — nothing is written over git |
--transport git | The REST reads and the merge-request confirmation only | OCX_ANNOUNCE_GIT_TOKEN when set, otherwise this token |
A GitLab CI job token (CI_JOB_TOKEN) has no write access through the API — it is read-only for branches, commits, raw files, merge requests and tags. That is the reason the git transport exists: a job token may write to the repository over HTTP when the index project allows job-token pushes and its job-token allowlist admits the publishing project. A bare job token cannot read either setting — both live behind endpoints closed to it — so ocx pushes and lets GitLab's own rejection decide; the split credential pair, whose API half is an ordinary token, reads both up front instead. Either posture exits 86 on a missing capability, but only the pre-push check names which one — the post-push rejection carries no field saying which setting was missing. GitLab then creates the merge request from the transmitted options rather than from an API call. Under --transport git inside a GitLab job (GITLAB_CI set to a non-empty value), an unset or empty OCX_ANNOUNCE_TOKEN falls through to CI_JOB_TOKEN automatically. Under --transport api it does not, because a job token cannot open the request there: that run needs a real access token in a masked variable.
# GitHub
export OCX_ANNOUNCE_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# GitLab
export OCX_ANNOUNCE_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxxTogether with OCX_ANNOUNCE_GIT_TOKEN this is the whole credential surface for a forge write — neither token ever enters the registry credential store ocx login writes to. This one is sent as a request header — Authorization: Bearer on GitHub, PRIVATE-TOKEN on GitLab, or JOB-TOKEN when the resolved credential is this job's own CI_JOB_TOKEN — never logged and never placed in a URL. Under --transport git with no OCX_ANNOUNCE_GIT_TOKEN set it is additionally presented to git as the secret half of an HTTP Basic pair, through git's own configuration environment. Redirects are disabled on the forge client, so a cross-host redirect cannot replay the header at another host. A forge's error body is echoed back in diagnostics, so the token is redacted out of it first — a reverse proxy that reflects request headers cannot put your credential in a CI log. The host in --index-repo and --fork must be a well-formed hostname; anything that could shift the URL's authority (userinfo, a query, a path) is refused rather than interpreted. Any mode other than --out fails immediately (exit 80) when the credential ladder resolves nothing, rather than falling back to an unauthenticated attempt. Without --fork the credential must also carry write permission on the index repository, which the run verifies before writing anything.
OCX_AUTH_<REGISTRY>_TYPE
The authentication type for the registry.
Valid values are:
basictoken(orbearer)anonymous
OCX_AUTH_<REGISTRY>_USER
Configures the username for the registry when using basic authentication. The corresponding password should be configured using the OCX_AUTH_<REGISTRY>_TOKEN variable.
This value is ignored if the OCX_AUTH_<REGISTRY>_TYPE is not set to basic.
OCX_AUTH_<REGISTRY>_TOKEN
Configures the access token for the registry. For basic authentication, this value will be used as the password.
This value is ignored if the OCX_AUTH_<REGISTRY>_TYPE is not set to bearer or basic.
OCX_BINARY_PIN
Absolute path to an ocx executable. Set automatically by the running ocx on every subprocess it spawns so child ocx invocations — most importantly, the inner ocx launcher exec call inside a generated entrypoint launcher — pin to the same binary that installed the package, instead of falling back to whatever $PATH happens to resolve at the launcher site.
export OCX_BINARY_PIN=/usr/local/bin/ocx$env:OCX_BINARY_PIN = "C:\Users\ci\.ocx\bin\ocx.exe"When OCX_BINARY_PIN is unset, generated launchers fall back to $PATH-resolved ocx. Set it manually only when running a launcher outside an outer ocx invocation and you want to pin a specific binary (typical use: a wrapper that records arguments, or a release-candidate binary tested side-by-side with the installed one).
Windows .exe shim behavior. The native Windows .exe shim — the sole Windows launcher — honors this variable with Windows IF DEFINED semantics: if OCX_BINARY_PIN is defined in the environment, even as an empty string, the shim uses its value as the program path. Only when the variable is completely unset does the shim fall back to ocx via PATH. This differs from the Unix launcher's ${OCX_BINARY_PIN:-ocx} form, which treats an empty value as unset.
OCX_CONFIG
Path to an extra configuration file to load. The file layers on top of the discovered tier chain (system, user, $OCX_HOME/config.toml) at highest file-tier precedence — it does not replace them. Use it to refine ambient config without rewriting it, or combine with OCX_NO_CONFIG for a fully hermetic load.
Equivalent to the --config CLI flag, but injectable via environment — the intended use is CI and Docker setups where the env is controlled but the command line is not.
export OCX_CONFIG=/etc/ocx/ci.tomlIf both OCX_CONFIG and --config are set, both load — --config sits at the highest file-tier precedence and wins on conflicting scalars. Missing files produce a clear error with the path.
Escape hatch: setting this to the empty string (OCX_CONFIG=) is treated as unset, not as an error. Useful when the variable is exported from a shell profile and you want to disable it for a single invocation without unsetting it.
OCX_CONSENT_PATHS
Grants the per-prompt shell hook permission to activate inside one or more project directories — or the subtrees beneath them — without waiting for a consent stamp from ocx shell allow or an explicit ocx add / ocx lock / ocx pull run in that checkout. OS PATH-separator-delimited (: on Unix, ; on Windows), mirroring mise's MISE_TRUSTED_CONFIG_PATHS.
export OCX_CONSENT_PATHS=/workspaces/acme-monorepo:/workspaces/acme-toolsEach entry is either an exact canonicalized directory, or — with a trailing /* — that directory and every project beneath it. This is modelled on git's safe.directory, with one difference: the subtree form here grants the named directory itself as well as everything beneath it, where git's /* covers only the repositories nested under it. The match is component-wise rather than a string prefix: /workspaces/acme/* covers /workspaces/acme/tools and never an attacker-planted /workspaces/acme-evil sibling. A * naming no directory (a bare *, or /*) grants nothing: git spells that "trust every repository on this machine", and OCX has no such token, for the same reason OCX_CONSENT_NAMESPACES has no whole-registry one. A trailing /* is the only wildcard: ?, […] and every other glob metacharacter is an ordinary filename byte here, so /workspaces/acm? grants a directory literally named acm? and never /workspaces/acme. Quote the value wherever it appears as a command word — env OCX_CONSENT_PATHS='/workspaces/acme/*' ocx … — because the shell expands an unquoted * there before OCX ever sees it, and zsh refuses the command outright with no matches found. An export VAR=… assignment, a Dockerfile ENV, and a devcontainer or CI declaration all pass the * through untouched. A subtree entry also activates a directory that does not exist yet, at the moment it is cloned, with no further gesture — the intended shape for a devcontainer or CI image built before the checkout it will run against exists. A leading ~ in an entry is expanded against the current user's home directory, the same interpolation git's safe.directory applies; %(prefix) is still not interpolated. The expansion is textual, not a symlink resolution: ~/dev/* where dev is itself a symlink onto a different mount still fails to match the project's canonical directory, because only the leading ~ is substituted and the rest of the entry — like every other entry — is compared as written, never canonicalized. There is also no ~user/… form; only the current user's own home expands. The entry side is otherwise never canonicalized, so a symlinked route to a project does not match either. See Shell Integration → Consent grants for the full activation predicate, including how this grant interacts with a stamp and with OCX_CONSENT_NAMESPACES.
export OCX_CONSENT_PATHS='/workspaces/*'This is the primary intended use case for a devcontainer feature or a CI image: the operator building the image knows the checkout path in advance and can pre-authorize it, even though the sources that checkout's ocx.lock will resolve against are not known at image-build time.
Additive only — unioned with any [shell.consent] paths entries from config.toml, never a replacement and never higher-precedence. An unset or empty value grants nothing; it is never read as "any directory."
Neither consent variable reaches the global toolchain. $OCX_HOME/ocx.toml, its [env], and every package it locks are always consented — it is your own file, so it needs no grant and no grant can withhold it. It composes on every prompt even while the project you are standing in is inert. Consent gates projects only.
OCX_CONSENT_NAMESPACES
Grants the per-prompt shell hook permission to activate for a project whose entire lock names sources inside pre-approved OCI namespaces, without a directory grant and without a per-project stamp. Comma-separated (a registry host may itself carry a :port, so : is not usable as the separator).
export OCX_CONSENT_NAMESPACES=ocx.sh/acme-corp,ghcr.io/acme-tools/*Each token names one source — <registry>/<first-path-segment>, exactly two components. ocx.sh/acme-corp and ocx.sh/acme-corp/* name the identical set. There is no whole-registry token: ocx.sh/* and a bare ocx.sh are both rejected, because a grant covering every organization on a host trusts every publisher on it, including anyone who can register there. Grant the organizations one at a time. A pattern with * anywhere but that trailing position, an uppercase byte, or an @ is rejected too — the whole variable's contribution is discarded with one warning rather than partially applied. A trailing or doubled comma is a separator artifact, not a pattern: empty tokens are dropped before matching, never treated as a catch-all.
This answers a different question than OCX_CONSENT_PATHS does: whose binaries may reach my PATH, rather than which checkout may activate at all. See Shell Integration → Consent grants for why both exist and how a project satisfies either one independently. Additive only, same rule as OCX_CONSENT_PATHS: unioned with [shell.consent] namespaces from config.toml, and an unset or empty value grants nothing.
The match is against the package store's record, not against the lock's text. ocx.lock is project-supplied text, so a clone naming a listed organization proves nothing on its own. What is matched is the store's own record of the coordinate each locked digest was materialized under on this machine — so a lock that borrows a listed organization's name for content this machine never pulled under it is refused, and ocx shell state names the disagreement. Writing that record takes an act of pulling here under that name: on a machine that has not cached the digest the bytes come off the wire under it, and where the layer cache already holds them one ocx pull naming the granted organization is enough. A project whose tools this machine has not fetched yet is simply inert until the first ocx pull, which writes a per-project consent stamp anyway — unless that pull declined to, under OCX_NO_CONSENT or --no-consent. What the grant still cannot confirm is who published the bytes: publishing into a listed organization needs that organization's publish credential and nothing more, and the recorded coordinate is the one you named rather than the endpoint your own [mirrors] or index routing dialled. See Shell Integration → What consent does not cover. List organizations whose publish credentials you actually control, and no others.
This grant does not extend to a project's own [env] table. It authorizes the tools ocx.lock resolved, never the [env] entries a project's own ocx.toml declares — that table has no publisher at all, so a relative type = "path" value works from clone content alone, with no registry served bytes to record. A namespace-granted project that also declares [env] still gets its tools; OCX withholds the table and prints a hint naming the fix: run ocx pull there once (which also writes a consent stamp, unless OCX_NO_CONSENT or --no-consent declines it — in which case the pull leaves the project exactly as inert as it found it), or list this exact directory — not a subtree — in [shell.consent] paths, which authorizes the [env] table too. See Shell Integration → What consent does not cover.
OCX_DEFAULT_REGISTRY
The default registry to use when no registry is specified in a package reference on the command line. Overrides the [registry] default key in the configuration file. If neither is set, OCX uses ocx.sh.
OCX_GLOBAL
Selects the global toolchain tier — equivalent to the --global CLI flag, but injectable via environment for CI and container setups where the command line is not controlled.
When set to a truthy value, toolchain-tier commands (add, remove, lock, update, pull, exec, env) target $OCX_HOME/ocx.toml instead of a discovered project file. Equivalent to passing the root flag --global (before the subcommand): ocx --global add ripgrep:14.
export OCX_GLOBAL=1This variable is resolution-affecting: it is forwarded to every subprocess ocx spawns via apply_ocx_config, so child invocations — generated launchers, nested ocx exec calls — see the same tier selection.
No implicit fallback, for these tier-selecting commands only: the earlier implicit $OCX_HOME/ocx.toml discovery (home-tier fallback) has been removed. add/remove/lock/update/pull/exec/env target the global toolchain only when --global is explicitly passed or OCX_GLOBAL is set — absent both, they resolve against a discovered project file, never a silent home-tier fallback. This is a statement about which file a command targets, not about whether the global toolchain is active: the per-prompt shell reconciler composes it into the live environment unconditionally, with neither flag set — see OCX_CONSENT_PATHS above.
OCX_GLOBAL and OCX_PROJECT are mutually exclusive — setting both is a usage error (exit 64).
Strict isolation: the global toolchain never composes into project-tier resolution. ocx exec and ocx package exec are hermetic and never see global tools. Isolation is enforced by PATH precedence — project tools prepend before global tools when a project toolchain is active. See Environment Composition — Strict isolation for the full model.
WARNING
This variable is mostly intended for testing. It is recommended to specify the registry explicitly in the package reference.
OCX_HOME
The root directory for all OCX data — the object store, local index, and install symlinks. If not set, defaults to ~/.ocx.
export OCX_HOME="/opt/ocx"OCX also discovers a configuration file at $OCX_HOME/config.toml — see the OCX home tier in the Configuration in-depth page.
OCX_IDENTITY_TOKEN
OIDC identity token for ocx package sign and ocx package attest — both share the same token-resolution path. Provides the lowest-precedence token source when no explicit override is given — used only if --identity-token-file and --identity-token-stdin are both absent. Useful in CI systems that inject OIDC tokens via environment rather than files.
The value must be a short-lived JWT issued by a supported OIDC provider (GitHub Actions, GitLab CI, CircleCI, etc.). The token is consumed once and never logged or written to disk.
Never forwarded to child processes. OCX reads OCX_IDENTITY_TOKEN directly inside the shared sign/attest token resolver, never places it in a child process environment via OcxConfigView, and strips it from every subprocess it spawns, plugins (ocx-<name>) included. Security rationale: OIDC tokens are short-lived bearer credentials — forwarding them into every subprocess child env would broaden the attack surface unnecessarily.
Token precedence for ocx package sign and ocx package attest (highest to lowest):
--identity-token-file <PATH>— file must have mode0600or tighter--identity-token-stdinOCX_IDENTITY_TOKEN- Ambient CI detection — GitHub Actions (
ACTIONS_ID_TOKEN_REQUEST_URL+ACTIONS_ID_TOKEN_REQUEST_TOKEN), GitLab CI (SIGSTORE_ID_TOKEN), CircleCI (CIRCLE_OIDC_TOKEN_V2) - Interactive browser OAuth (suppressed with
--no-tty)
Short-lived tokens only
OIDC identity tokens expire quickly (typically under 10 minutes). Do not store a token in a long-lived environment or secret manager entry — fetch a fresh token immediately before calling ocx package sign or ocx package attest.
OCX_KEY_PASSWORD
The password guarding an encrypted private key — a cosign ENCRYPTED SIGSTORE PRIVATE KEY PEM envelope — for OCX's key-mode signing backend.
Never a flag: a password in argv is visible to every process on the host. An unset variable reads as the empty password — the shape a key pair has when cosign generate-key-pair is answered by pressing enter twice — so leaving it unset is a real, supported case, not a distinct error. The value is not zeroized after use: it already lives in this process's environment block, so wiping a copy of it protects nothing.
Never forwarded to child processes. OCX reads this variable directly and strips it from the environment of every subprocess it spawns, plugins (ocx-<name>) included.
It opens the key; it never names one. Pair it with --key <path> or with OCX_SIGNING_KEY — the two are unrelated and coexist.
OCX does not generate key pairs itself. Run cosign generate-key-pair from an activated environment — cosign ships in the OCX index — to produce a cosign.key / cosign.pub pair, optionally password-protected.
OCX_SIGNING_KEY
The signing key itself, for --key env://OCX_SIGNING_KEY. The variable holds the PEM — a cosign ENCRYPTED SIGSTORE PRIVATE KEY envelope when signing, an SPKI public key when a [[trust.policy]] signer names it — never a path to a file holding one.
This is the spelling for a runner with no writable disk, or one where writing the key out in order to sign with it is the thing being avoided. Unset or empty is refused with exit 74 naming the variable, the same code a missing key file gets; a value larger than 64 KiB is refused with exit 65, the same cap a key file has.
export OCX_SIGNING_KEY="$(cat cosign.key)"
export OCX_KEY_PASSWORD='…'
ocx package sign --key env://OCX_SIGNING_KEY ocx.sh/acme/tool:1.0.0Never forwarded to child processes. OCX reads this variable directly and strips it from the environment of every subprocess it spawns, plugins (ocx-<name>) included.
Any other variable name is inherited
env:// accepts any variable name, and only this one is on OCX's credential list. A key in MY_KEY is passed straight through to every plugin and generated launcher OCX spawns, because nothing distinguishes that name from any other variable in the environment. Use OCX_SIGNING_KEY unless you have a specific reason not to.
OCX_SIGSTORE_TRUSTED_ROOT
A bare path, or a file:// one, naming a Sigstore trusted-root JSON document — or a directory containing trusted_root.json — that ocx package verify loads its trust material from. Equivalent to the --sigstore-trusted-root flag; the flag takes precedence when both are set.
A trusted root carries three things together: the Fulcio CA certificate(s), the certificate-transparency log keys, and the pinned Rekor public key. All three are load-bearing — a Fulcio certificate embeds a Signed Certificate Timestamp that the verifier checks against the CT log's key, so trust material carrying CA anchors alone is refused up front with exit 78 and the message trust root carries no CT log key.
This is the air-gapped seam: point it at a local trust-root mirror and verify against a private Sigstore deployment with no Sigstore-services network. The file is read as-is — supplying it deliberately bypasses the TUF fetch that runs when no override, no configured trust root, and no cache are present, so keeping it current is the operator's job.
Combined with OCX_OFFLINE, this is the fully offline path: the pinned Rekor key means the Signed Entry Timestamp verifies without contacting Rekor.
This variable is one rung of a six-rung ladder: the --sigstore-trusted-root flag, this variable, [trust.sigstore] in config.toml, $OCX_HOME/sigstore/trusted-root.json, the trust-root cache under $OCX_HOME/state/trust_root/, and finally the public-good Sigstore root fetched over TUF (cached under $OCX_HOME/state/tuf/). Setting an env var on every machine is the most expensive way to reach a private stack — for a fleet, publish the trust root once and let it arrive through configuration instead: see Self-hosted Sigstore.
This variable affects only the local verify operation and is not forwarded to subprocess children.
OCX_INDEX
Override the path to the local index collection directory. By default, OCX reads the local index from $OCX_HOME/index/ (typically ~/.ocx/index/) — a directory holding one subtree per source (ocx.sh/, ghcr.io/, …).
export OCX_INDEX="/path/to/bundled/index"Setting this variable swaps the whole collection for a shipped one — never a partial overlay of the two. It is intended for environments where an index copy is bundled alongside a tool rather than stored in OCX_HOME — for example inside a GitHub Action, Bazel Rule, or DevContainer Feature. A project that commits its own .ocx/index/ copy exports this variable for the project's shell, pointing at that committed directory. It is never set ambiently by ocx direnv export — an explicit --index or a deployment-set OCX_INDEX are the only channels that redirect it.
The command line option --index takes precedence over this variable. Under --remote or OCX_REMOTE, tag- and catalog-addressed lookups bypass the redirected collection and query the registry directly, so this variable has no effect on those. Digest-addressed lookups still consult the redirected collection first in every mode, so it keeps mattering for anything already pinned by digest.
OCX_PATCH_SNAPSHOT
Path to a snapshot file (patches.snapshot.json) that pins companion package digests.
export OCX_PATCH_SNAPSHOT="/workspace/patches.snapshot.json"When set, the compose overlay prefers the snapshot's pinned companion digests over live tag lookups, enabling reproducible builds without a network round-trip. Pins are per repository:tag, so one repository named at two tags freezes as two independent companions. Write the snapshot with ocx patch freeze (see command-line.md); a file whose format version this ocx does not read is refused (exit 65) with that same remedy.
Adopting a snapshot is a deliberate opt-in: this variable is the only selector, and it is independent of OCX_FROZEN / --frozen, which scope to the package tier. Freezing the patch tier means pointing this variable at a snapshot.
This variable is resolution-affecting and is forwarded to child ocx processes (such as generated entrypoint launchers) so they resolve the same frozen companion digests as the parent. Unset to disable snapshot-pinning and fall back to live lookups.
OCX_JOBS
Caps the number of root packages pulled in parallel — applies to every command that fans out through the package manager (install, pull, package pull, exec, env).
export OCX_JOBS=40 means "use all logical cores" (matches GNU parallel -j 0) and diverges from Cargo's --jobs 0, which errors. Negative or non-numeric values are ignored with a warning. Unset = unbounded (legacy default).
The command line option --jobs takes precedence over this variable.
OCX_LAZY_MODE
The lowest tier of the lazy-mode resolution ladder — whether a declared tool composes eagerly (content fetched before it reaches PATH) or as a shim that defers content until its first invocation.
export OCX_LAZY_MODE=alwaysAccepts never (the default when nothing sets any tier) or always, parsed case-insensitively (Always, ALWAYS, and always are equivalent — unlike the --lazy-mode flag and the ocx.toml key, which are both case-sensitive lowercase). An unrecognized value is ignored with a warning, the same as an unset variable — this tier is simply absent and resolution continues to the ladder's floor.
Four more-specific tiers can override this variable: the --lazy-mode flag, [package."<id>"], [group.<name>], and the toolchain-level lazy-mode key, all in ocx.toml. See Deferred Tools for the full ladder and Project Configuration for the ocx.toml keys.
Not forwarded to child processes
Unlike the resolution-affecting variables listed in the box at the top of this section (binary path, offline, remote, config file, index), OCX_LAZY_MODE does not propagate from a parent ocx into a subprocess — it changes when content materializes, never which digest resolves, so it sits outside the forwarded set entirely. A child ocx invocation reads its own environment.
OCX_LAZY_REPORT
Whether a deferred tool's first-invocation download renders progress. Read inside the hidden ocx launcher shim subcommand — the process a generated shim launcher execs into — never by the command that composed the environment in the first place.
export OCX_LAZY_REPORT=progressAccepts silent (the default) or progress, parsed case-insensitively like OCX_LAZY_MODE above. progress opens a channel on the controlling terminal; where none is reachable — a Docker build, a CI runner, anything under setsid — it silently falls back to silent rather than erroring. Unrecognized values are ignored with a warning, same as unset.
Three more-specific tiers can override this variable: the --lazy-report flag (declared only on ocx launcher shim; a user never types it directly), [package."<id>"], and the toolchain-level lazy-report key, both in ocx.toml. There is no [group.<name>] tier for lazy-report — see Deferred Tools for why. Not forwarded to child processes, for the same reason as OCX_LAZY_MODE above.
OCX_INSECURE_REGISTRIES
A comma-separated list of registry hostnames (with optional port) that should be contacted over plain HTTP instead of HTTPS.
export OCX_INSECURE_REGISTRIES="localhost:5000,registry.local:8080"The variable is one half of a union with the [registries.<name>] insecure config key: a host named in either source is plaintext-eligible. Only one thing narrows that set: an insecure = false entry declared at the system config scope subtracts its host from this variable's contribution too. Every other insecure value — including an explicit false below the system scope — only ever adds to the union, never removes from it. Prefer the config key for a host your machine always talks to over plain HTTP; the variable is the ambient, per-invocation half, and it is what a subprocess inherits.
Names are matched exactly, host[:port] together — the same comparison the transport makes. registry.corp does not cover registry.corp:5001.
The same set gates a second case: a plain-http:// [mirrors] target. A mirror value — for either the registry role or the index role — that starts with http:// is refused unless the mirror's own host is in the set; https:// mirror targets never need it, regardless of which role they cover.
WARNING
This disables TLS for the listed registries, so credentials travel the wire in the clear. Only use it for local development registries that do not support HTTPS.
It covers transport and nothing else: an HTTPS registry that names a plaintext token-service realm is still refused, an HTTPS request redirected to http:// is refused rather than followed, and listing one host never licenses cleartext for another. See Redirect Refusals for what these refusals look like from the command line and why widening this list does not change them.
OCX_MIRRORS
A JSON object that maps hosts to mirror endpoints. Each key is a host being redirected; each value is either a plain string — redirecting both the registry and index roles for that host — or an object {"registry"?: "...", "index"?: "..."} that splits the two, the same union [mirrors] takes in the config file. Each role's value has the form scheme://host[/repo-key-prefix].
export OCX_MIRRORS='{"ghcr.io":"https://artifactory.example.com/ghcr-remote","index.ocx.sh":{"index":"https://artifactory.corp/ocx-index"}}'This variable is resolution-affecting: it is forwarded to every subprocess ocx spawns via apply_ocx_config, so child invocations — generated launchers, nested ocx exec calls — see the same mirror map.
OCX_MIRRORS overrides the [mirrors] config key on a per-host, per-role basis. A role present in a host's OCX_MIRRORS entry replaces the config entry for that role only; roles and hosts absent from OCX_MIRRORS still come from [mirrors] in the config file.
One thing it cannot do is revoke a verified index path. A registry-role entry in a config file the operator controls suppresses the compiled-in index for that namespace; the same entry arriving through OCX_MIRRORS redirects traffic but leaves the index in place. OCX_MIRRORS='{"ocx.sh":"…"}' therefore still resolves ocx.sh/… through index.ocx.sh — a forwarded environment variable must not be able to drop a namespace off the index's digest verification and yank gate.
Malformed values abort at startup
A malformed OCX_MIRRORS value is a hard startup error. OCX aborts with an error message naming the problem:
- The value is not valid JSON
- A per-host value is neither a string URL nor an object with only
registry/indexstring fields - A mirror value starts with
http://but the mirror host is not plaintext-eligible — neitherOCX_INSECURE_REGISTRIESnor aninsecure = trueentry in[registries.<name>]names it
OCX never silently continues with an empty mirror map when OCX_MIRRORS is set — falling back to no mirrors would silently route reads to the firewall-blocked origin, which is the exact failure replace semantics are designed to prevent.
For the full mirror semantics (replace behavior, auth, lockfile portability, interaction with --offline), see the [mirrors] configuration reference.
OCX_PATCHES
A JSON object encoding the resolved [patches] tier. OCX serialises the active patch configuration into this variable and forwards it to every subprocess it spawns so child invocations — generated launchers, nested ocx exec calls — apply the same companion overlay.
# Managed by OCX; usually not set manually. Shape produced by `apply_ocx_config`:
export OCX_PATCHES='{"registry":"registry.corp.example/ocx-patches","path_template":"{registry}/{repository}","required":true,"system_required":false,"no_patches":[]}'no_patches carries the forwarded project per-package opt-out: ocx exec injects the opted-out registry/repository keys plus, for each opted-out base actually resolved that run, its content digest — a generated launcher resolves its own base via a synthetic content-addressed identifier with no real registry/repository, so the digest is what a launcher's re-entry (ocx launcher exec) matches against. This lets a tool launched through ocx exec honor the project's no-patches opt-out even after it re-enters ocx through its own launcher. An empty (or absent) no_patches array is the byte-identical equivalent of no opt-out being forwarded at all.
This variable is resolution-affecting and is forwarded to every subprocess automatically. Manually setting it overrides the [patches] config-file tier for that subprocess tree.
Malformed values abort at startup
A malformed OCX_PATCHES value (not valid JSON, or missing the registry field) is a hard startup error. OCX aborts rather than silently running without the operator- mandated companion overlay — a fail-closed posture that mirrors OCX_MIRRORS behavior.
For the full patch semantics (descriptor format, companion installation, per-package opt-out), see the [patches] configuration reference and the Patching packages guide.
OCX_ENV
A JSON payload encoding the composed project and group [env] entries plus any --env overrides — stages 4 through 6 of project environment precedence. A --env override may carry any of the three kinds — constant, path, or list — the same as a project- or group-declared entry; a list entry additionally carries the separator it settled on during composition. OCX forwards it to every subprocess it spawns, most importantly the inner ocx launcher exec call inside a generated entrypoint launcher, so a tool invoked through a launcher sees the same project-level overrides as the process that spawned it rather than silently reverting to the package's own values.
# Managed by OCX; not set manually.
export OCX_ENV='[...]'This variable is resolution-affecting and is forwarded automatically; manually setting it overrides the project/group [env] tier for that subprocess tree. It carries no envelope version. Instead, each forwarded entry's own kind (constant, path, or list) is validated strictly on decode: an ocx binary receiving a kind it does not recognize — from a newer release, or a forged value — fails closed on the whole payload rather than defaulting the unknown entry to a constant, which would silently apply the wrong semantics. A list entry additionally requires a usable separator — non-empty, and free of =, a newline, or a carriage return — and a value that does not start or end with it; a missing, unusable, or edging separator fails the payload the same way.
Malformed values abort at startup
A malformed OCX_ENV value — invalid JSON, or an entry with an unrecognized kind — is a hard startup error, mirroring OCX_PATCHES and OCX_MIRRORS. Setting OCX_DEFAULT_REGISTRY or any other OCX_*/__OCX_* key through it has no effect: those keys are rejected before the payload is ever built (see below) and rejected again on decode, so a forged OCX_ENV cannot reach ocx's own resolution surface.
A stale OCX_ENV inherited from a parent shell is removed before a child's own project/group [env] is applied, so it cannot leak into an unrelated invocation.
OCX_* and __OCX_* keys cannot be set from ocx.toml. The project [env] table and every [group.<name>.env] reject any key starting OCX_ or __OCX_ at parse (exit 78); the ocx exec --env flag rejects the same keys at flag-parse (exit 64). Without this, a checked-in file could set OCX_DEFAULT_REGISTRY, OCX_INDEX, OCX_OFFLINE, or any other variable in this reference and reconfigure how ocx itself resolves for every contributor who clones the repository — this is the same forwarding mechanism OCX_ENV uses above, closed at the source rather than only on decode.
OCX_MANAGED_CONFIG
The OCI reference for the [managed] corporate-configuration artifact. Overrides [managed] source for this invocation only — never written back to the seed. The persistent seed is written only by ocx config setup and ocx self setup --managed-config.
export OCX_MANAGED_CONFIG=internal.company.com/ocx-config:ciThis variable is resolution-affecting: it is forwarded to every subprocess ocx spawns via apply_ocx_config, so child invocations — generated launchers, nested ocx exec calls — resolve the same managed tier.
Runtime OCX_MANAGED_CONFIG="" is treated as unset, matching the OCX_CONFIG precedent — useful when the variable is exported from a shell profile and you want to disable it for a single invocation without unsetting it.
Suppressed entirely (read side) by OCX_NO_CONFIG — hermetic means hermetic.
For CI, pair the env var with an explicit sync step — resolving the tier never triggers a fetch on its own:
export OCX_MANAGED_CONFIG=internal.company.com/ocx-config:ci
ocx config update
ocx package install kitware/cmake:3.28See [managed] for the full tier semantics and ocx config update for the sync command.
OCX_NO_CONFIG_REFRESH
When set to a truthy value, disables the background refresh tick for the [managed] configuration tier — both the apply and notify refresh postures are silenced. An explicit ocx config update still runs and still updates the snapshot; only the automatic per-command probe is suppressed. It does not gate the setup-time re-sync either: ocx self setup and ocx config setup still reconcile an already-adopted seed on every invocation regardless of this variable — use --offline to skip that re-sync instead.
export OCX_NO_CONFIG_REFRESH=1The background refresh tick is also automatically suppressed — independent of this variable, with no snapshot fetch even attempted — when:
CIis set to a truthy valueOCX_OFFLINEis set to a truthy value (or--offlineflag)- stderr is not a terminal (e.g., piped or redirected)
This mirrors the OCX_NO_UPDATE_CHECK auto-check gate exactly. OCX_NO_CONFIG_REFRESH is the explicit kill switch; the three conditions above suppress the tick even when the variable is unset.
Distinct from OCX_NO_UPDATE_CHECK: that variable silences the ocx-binary self-update notice, an unrelated concern independently silenceable from the managed-config tick.
OCX_LOG
The log level for OCX. You can set this variable to the same values as the --log-level command line option (e.g. warn, info, etc.). If --log-level is specified, it will take precedence over this environment variable. For more information on log levels, see the command line reference.
OCX_LOG_CONSOLE
Similar to OCX_LOG, but specifically for configuring the log level of messages emitted to the console. If OCX_LOG_CONSOLE is set, it will take precedence over OCX_LOG for console messages.
OCX_NO_CONFIG
When set to a truthy value, OCX skips the discovered configuration chain — no user or $OCX_HOME/config.toml is loaded. It also suppresses the [managed] snapshot candidate entirely and disables the OCX_MANAGED_CONFIG env-override read — hermetic means hermetic, so a managed tier cannot slip in through either the local snapshot or the env override while this variable is set. Explicit paths supplied via --config or OCX_CONFIG still load, because they represent deliberate intent rather than ambient environment.
A SYSTEM-scope policy is not ambient configuration, and this variable does not suppress it. /etc/ocx/config.toml still loads under OCX_NO_CONFIG=1 — and, on this path as on the ordinary one, a system file that exists and cannot be read aborts the invocation (exit 78) instead of being skipped, since skipping it would drop the very policy this paragraph is about. What loads is filtered down to only the sections an operator locked there — a locked [registry], [registries.<name>], [mirrors], [patches], or [records] section still applies. Only the [managed] tier is dropped unconditionally, since it is itself a mechanism for pulling in ambient state. The distinction is deliberate: OCX_NO_CONFIG exists so a caller can opt out of config that leaked in from a runner image or a mounted home directory, not so a caller can opt out of a policy the operator declared with root access to /etc/ocx/config.toml.
Use this for CI reproducibility: locked workflows should ignore any ambient config that might leak in from the runner image or a mounted home directory.
Combined with an explicit path, this is the canonical hermetic pattern:
OCX_NO_CONFIG=1 ocx --config /ci/ocx.toml install cmake:3.28OCX_NO_CONFIG is available only as an environment variable. A --no-config CLI flag would duplicate surface without solving a new problem: the hermetic-CI use case is best expressed via env vars, which are how CI systems already inject policy. A flag would require callers to both export the env var and pass the flag in every per-command invocation — two sources of truth for the same intent.
OCX_NO_COMPLETIONS
When set to a truthy value, ocx self activate skips the shell-completion injection block. PATH prepend and global toolchain env eval still run.
Use this when you manage completions through a separate framework (e.g. oh-my-zsh or a Nix-generated completion store) and do not want OCX to overwrite them on every shell start.
export OCX_NO_COMPLETIONS=1This variable has no effect on ocx shell completion, which always generates the completion script regardless.
OCX_NO_HOOK
When set to a truthy value, disables the per-prompt shell reconciler entirely. ocx self activate still runs at shell start — PATH prepend, completions and the global toolchain env eval are unaffected — but it registers no per-prompt hook, so nothing in this shell recomposes on cd, on a lock change, or after ocx update. See Shell Integration for what the hook does when it is not disabled.
export OCX_NO_HOOK=1This is a boolean, not a tri-state — there is no OCX_HOOK=0|1|auto — mirroring every other OCX_NO_* toggle in this reference (OCX_NO_CONFIG, OCX_NO_COMPLETIONS, OCX_NO_MODIFY_PATH, OCX_NO_PROJECT, OCX_NO_VERIFY). The positive channel is the --hook flag on ocx self setup and ocx self activate, and an unset variable already means "auto" (on, for an interactive shell).
Read once, at shell start — never on the per-prompt path. Exporting OCX_NO_HOOK=1 mid-session takes effect at the next shell start, not the next prompt: the per-prompt reconciler's own budget forbids reading configuration or environment toggles on every prompt, so whether the hook runs at all is decided once, when the shell's activation shim sources ocx self activate.
The only gesture that makes a shell wholly inert. OCX_NO_CONFIG empties the discovered configuration chain and suppresses the managed tier, but it does not touch an explicit-tier --config / OCX_CONFIG file or the OCX_CONSENT_PATHS / OCX_CONSENT_NAMESPACES grants — any of those can still activate the hook for a consented project even with OCX_NO_CONFIG=1 set. OCX_NO_HOOK=1 is the one variable that shuts the whole mechanism off regardless of what else is configured.
OCX_NO_UPDATE_CHECK
When set to a truthy value, OCX will not check the remote registry for newer versions on CLI startup. By default, OCX prints a notice to stderr if a newer version is available in the remote registry.
The update check is also automatically suppressed when:
CIis set to a truthy valueOCX_OFFLINEis set to a truthy value (or--offlineflag)- stderr is not a terminal (e.g., piped or redirected)
- the command is
version,about, orshell completion
To disable the check entirely (including suppressing the throttle state write), use this variable. To change the check frequency instead of disabling it, use OCX_UPDATE_CHECK_INTERVAL.
OCX_NO_PROJECT
When set to a truthy value, OCX skips project-tier discovery — the CWD walk for ocx.toml is pruned and OCX_PROJECT is ignored. Explicit paths supplied via --project still load, because they represent deliberate intent rather than ambient environment.
Use this for CI reproducibility when you want to run ocx inside a repository without the project-tier toolchain influencing the invocation.
Combined with an explicit path, this mirrors the hermetic pattern used for OCX_NO_CONFIG:
OCX_NO_PROJECT=1 ocx --project /ci/ocx.toml exec -- cmake --versionOCX_NO_PROJECT is available only as an environment variable. A --no-project CLI flag would duplicate surface without solving a new problem — the hermetic-CI use case is best expressed via env vars, matching the OCX_NO_CONFIG pattern.
OCX_NO_CONSENT
When set to a truthy value, the seven commands that record a shell-activation consent stamp as a side effect — add, remove, lock, update, pull, exec and init — run without writing one. Nothing else changes: the pull still pulls, the child process still runs, the lock is still written.
OCX_NO_CONSENT=1 ocx --project /work/checkout/ocx.toml pullSet it wherever a machine drives OCX against a checkout its operator did not choose. Running a mutating command in a directory is normally consent, and that is the right default for a person at a keyboard — but a Bazel repository rule, a CI job or a generated launcher makes the same gesture on someone's behalf, and the stamp it leaves authorizes that project's [env] table on every later cd once the shell hook is installed. This variable is how automation declines to answer a question it was never asked.
ocx shell allow ignores it. That command is the explicit gesture this variable exists to tell machine invocation apart from, so it always writes the stamp; ocx shell revoke is still how you take one back.
ocx init, ocx pull and ocx exec also carry a --consent / --no-consent flag pair, and the flag outranks this variable — OCX_NO_CONSENT=1 ocx pull --consent records a stamp. The variable decides only where no flag was given, which is what makes it usable as a blanket setting for a whole pipeline while a single step opts back in.
It is forwarded to child ocx processes, so a script run under ocx exec that itself calls ocx pull inherits the same answer rather than stamping on the way through — including across ocx exec --clean, which otherwise hands the child an empty environment.
ocx exec --no-consent sets this variable on the child environment too, so the flag reaches that nested ocx pull exactly as an exported variable would. A command line does not cross a spawn, so without it the explicit gesture would carry less far than the blanket one. The reverse is deliberately not true: ocx exec --consent does not clear a variable the child would otherwise inherit. --consent answers for the one project that invocation targets, not for whatever the child goes on to touch — refusal inherits downward, permission does not.
Exporting it in your own shell will make working projects go inert
A stamp records the OCI sources its project's lock resolved against, and growing the lock past that set re-confirms by re-recording. With OCX_NO_CONSENT exported globally, that re-record never happens: ocx add pulls in a tool from a source the stamp does not cover, nothing re-stamps, and the project you were working in five minutes ago is inert at the next prompt. That is the fail-safe direction, and it is still surprising. Set the variable in the pipeline, the image or the build rule that needs it — not in your interactive profile. ocx shell state names this state, and ocx shell allow clears it in one gesture.
OCX_HOME role in setup
When you run ocx self setup, it uses OCX_HOME (default ~/.ocx) as the root for two things: the location it writes the five env shim files (env.sh, env.fish, env.ps1, env.nu, env.elv), and the directory that the shims themselves resolve at runtime via shell-native assign-if-unset syntax (e.g. : "${OCX_HOME:=$HOME/.ocx}"). Setting OCX_HOME to a non-default path before running ocx self setup puts all shims and the object store under that path consistently. See OCX_HOME for the full data-directory reference.
OCX_NO_MODIFY_PATH
When set to a truthy value, ocx self setup writes the env shim files to $OCX_HOME and touches neither a shell profile nor the session PATH. Use this in CI environments or when you manage your PATH manually.
It suppresses both PATH surfaces, on all three platforms: the managed activation block in your shell profiles, and the session-level registration — the Windows user environment, a systemd environment.d drop-in on Linux, a login LaunchAgent on macOS. The writers are never called. Each location still appears in the run summary, with outcome skipped_opt_out, so you can see what was not touched.
The equivalent CLI flag is --no-modify-path on ocx self setup; the flag and this variable mean the same thing, and either one alone is enough.
Truthy values only — not "any non-empty"
OCX_NO_MODIFY_PATH follows the same truthy/falsy rules as OCX_OFFLINE and OCX_REMOTE. Only the values in the truthy list (1, y, yes, on, true, case-insensitive) enable the flag. An unrecognized non-empty value (e.g. OCX_NO_MODIFY_PATH=skip) logs a warning and is treated as the default (false — both PATH surfaces are written, the profile blocks and the session-level registration). An empty string is also treated as false.
The opt-out is not remembered between runs. A user who ran ocx self setup --no-modify-path once, then runs ocx self setup again without the flag, gets both the profile blocks and the session-PATH registration written. To make the opt-out persistent, either:
- Export
OCX_NO_MODIFY_PATH=1in your environment before everyocx self setupinvocation, or - Pass
--no-modify-patheach time.
A $OCX_HOME/state/no-modify-path sentinel file that persists the preference automatically is planned for a future release.
What you give up. Shell activation through $OCX_HOME/env.* takes effect only inside interactive PowerShell and POSIX shell sessions that source the login profile. cmd.exe, desktop launchers, IDEs and background services never source one, so with this opt-out set they never see the ocx directories.
Reaching those is exactly what the session-PATH registration is for, and it is the half this variable also suppresses. Without the opt-out, ocx self setup registers two directories, in this order:
$OCX_HOME/toolchain/active/bin— the global toolchain's launcher trampolines. It leads, so a global toolchain that pinsocxis the one a session resolves.$OCX_HOME/symlinks/<ocx cli id>/current/content/bin— where the installedocxbinary itself resolves from. It is the floor: the name a session falls back to when no toolchain pins it.
There is no $OCX_HOME/bin; the install directory is the symlink path above, derived from the store rather than joined from a literal.
If you keep the opt-out and still need ocx tools visible to cmd.exe or GUI applications, put those two directories on PATH yourself — via System Properties or Group Policy on Windows, an environment.d drop-in on Linux, a LaunchAgent on macOS.
OCX_NO_CODESIGN
When set to a truthy value, OCX will skip ad-hoc code signing of macOS binaries after installation. By default, OCX automatically applies ad-hoc code signatures to extracted Mach-O binaries on macOS, which is required for execution on Apple Silicon. See the FAQ for details on why this is necessary and how it works.
This variable has no effect on non-macOS systems.
OCX_NO_VERIFY
When set to a truthy value, OCX skips the policy-gated automatic signature verification on ocx package install and ocx package pull, and on every command that auto-installs on demand — ocx package exec, ocx package env, ocx exec, ocx env, and patch discovery. Only install and pull also carry a --verify/--no-verify flag; the others opt out via this variable alone. It follows the same truthy/falsy rules as OCX_OFFLINE.
By default, when a [[trust.policy]] covers a package being installed, OCX verifies its Sigstore signature at the metadata-first seam — after the manifest resolves, before any layer downloads — and aborts the install fail-closed if verification fails. This variable is the CI-wide opt-out. When a policy-covered package is skipped this way, OCX logs a single WARN per invocation so the bypass is never silent.
The per-command --no-verify flag mirrors this variable and wins over it: --verify on the command line re-enables verification even when OCX_NO_VERIFY is truthy. The opt-out is forwarded to subprocess children (a launcher-spawned child install inherits the same CI-wide setting), unlike the local-only OCX_SIGSTORE_TRUSTED_ROOT.
When no [[trust.policy]] covers a package, verification does not run regardless of this variable — trust is opt-in. See the user guide for the full model.
OCX_OFFLINE
When set to a truthy value, OCX disables all network access. Tag→digest resolution must be satisfied by the local index or by a digest-pinned identifier; unpinned tags missing from the local index error immediately. Useful for hermetic CI runs and air-gapped environments. The command line option --offline takes precedence over this variable.
Combined with OCX_REMOTE, enables pinned-only mode: no source contact, no local writes, and any tag-addressed resolution that cannot be satisfied locally errors instead of falling back.
For ocx package verify, offline scopes to the Sigstore trust services — the Rekor-key fetch and TUF — not the artifact registry, which verify still reads the signature from (a local mirror, in air-gapped deployments). Offline verify reuses cached or supplied trust material and must have a pinned Rekor key, so it comes from OCX_SIGSTORE_TRUSTED_ROOT or one of the other trust-root rungs, or from the $OCX_HOME/state/trust_root/ cache a prior online verify wrote. With no such material, verify fails with exit 78 naming the remedy — it never silently skips verification.
OCX_PROJECT
Path to a project-tier ocx.toml to load, or to the directory that holds it. Bypasses the CWD walk — a file is loaded directly, and a directory resolves to <dir>/ocx.toml. Not part of the ambient configuration chain: the project tier is a separate API surface from the ambient config tier loaded via OCX_CONFIG.
Equivalent to the --project CLI flag, but injectable via environment — the intended use is CI and Docker setups where the env is controlled but the command line is not.
export OCX_PROJECT=/workspace/ocx.tomlPrecedence: --project > OCX_PROJECT > CWD walk. OCX_NO_PROJECT=1 prunes both the CWD walk and this env var, but does not block an explicit --project flag. A path that does not exist at all exits with code 79. A directory that exists but holds no ocx.toml is not a missing file — it is no project — and exits with code 64, the same code ocx exec and its siblings use whenever they have no project to act on. A path that exists but cannot be read as configured — permission denied, or a candidate that is not a regular file — exits with code 74.
Escape hatch: setting this to the empty string (OCX_PROJECT=) is treated as unset, not as an error. Useful when the variable is exported from a shell profile and you want to disable it for a single invocation without unsetting it.
Symlink policy: explicit paths (this variable and --project) follow symlinks. The CWD walk rejects symlinked ocx.toml candidates — use --project or OCX_PROJECT to opt in.
OCX_QUIET
When set to a truthy value, OCX suppresses the structured stdout report that every command emits — tables in plain mode, the JSON document in --format json mode. Errors, warnings, and progress on stderr are unaffected.
The command line option --quiet takes precedence over this variable.
OCX_RECORDS_DIR
Sink directory for the exec-time resolution record — a JSON file OCX writes immediately before starting a tool, naming every package digest that composed the child's environment. Equivalent to the --records-dir flag on ocx exec and ocx package exec, and to the [records] dir config key, but injectable via environment for CI and container setups where the command line is not controlled.
export OCX_RECORDS_DIR=/var/log/ocx/recordsUnset at every tier — the default — means no record is written and no I/O is added to the exec path. This variable is resolution-affecting: it is forwarded to every subprocess ocx spawns via apply_ocx_config, so a generated launcher's re-entry (ocx launcher exec, which takes no flags of its own) writes to the same sink as the parent invocation.
Escape hatch: setting this to the empty string (OCX_RECORDS_DIR=) is treated as unset, matching the OCX_CONFIG precedent — useful when the variable is exported from a shell profile and you want to disable recording for a single invocation without unsetting it.
Overridden by --records-dir. A SYSTEM-scope [records] declaration locks the whole section, including dir, and this variable has no effect once locked. See Execution Records for the full precedence fold, the sink's no-clobber write behavior, and the failure posture when a record cannot be written.
OCX_RECORDS_NAME
Filename template for the exec-time resolution record. Equivalent to the --records-name flag on ocx exec and ocx package exec, and to the [records] name config key.
export OCX_RECORDS_NAME='{time}-{host}-{pid}.json'Accepts the same closed placeholder set as the config key — {time}, {pid}, {rand}, {host} — see Filename grammar. An unrecognized placeholder is a configuration error (exit 78) at resolve time, never a silently-unexpanded literal. Has no effect unless a sink is also active via OCX_RECORDS_DIR, --records-dir, or config [records] dir.
Escape hatch: setting this to the empty string (OCX_RECORDS_NAME=) is treated as unset, matching the OCX_CONFIG precedent.
This variable is resolution-affecting and forwarded to every subprocess ocx spawns, the same as OCX_RECORDS_DIR. Overridden by --records-name. Locked along with the rest of [records] under a SYSTEM-scope policy.
No OCX_RECORDS_REQUIRED
Recording's fail-open/fail-closed posture ([records] required) is config-file-only at every tier — never an environment variable, never a CLI flag. See Configuring [records].
OCX_REMOTE
When set to a truthy value, routes mutable lookups (tag list, catalog, tag→manifest resolution) to the remote registry instead of the local index. Pure-query commands (ocx index list, ocx index catalog, ocx package description pull) do not persist results to the local index — to refresh the snapshot, run ocx index update explicitly. Digest-addressed reads still consult the local index first (immutable content is safe to cache).
Equivalent to passing the --remote flag on every invocation. See the user guide for the routing model and the pinned-only mode (combined with OCX_OFFLINE).
OCX_FROZEN
When set to a truthy value, freezes tag→digest resolution to the local index. A tag already in the local index resolves from cache; a digest-pinned identifier (repo@sha256:…, or a tag pinned by ocx.lock) still fetches its content over the network. An unpinned tag missing from the local index errors with exit code 81 instead of being fetched and recorded — the guarantee that no unknown version is installed. Run ocx index update to populate the index first, with this variable unset: recording a new mapping is itself discovery, so a frozen index update is refused (exit 81).
It scopes to the package tier, whose pin is the local index. A patch companion resolves live regardless — patches float by design — and pins in the patch tier's own record ($OCX_HOME/state/patch-companions/), advanced by ocx patch sync and frozen deliberately with ocx patch freeze plus OCX_PATCH_SNAPSHOT. The managed-configuration tier is likewise unaffected: ocx config setup / ocx config update behave identically with and without this variable.
Unlike OCX_OFFLINE, this is not a network ban: known and digest-pinned content is still fetched. It only refuses to discover a new tag→digest mapping.
Equivalent to passing the --frozen flag on every invocation; the flag takes precedence. Mutually exclusive with OCX_REMOTE (combining the two is a usage error, exit 64); combining with OCX_OFFLINE is accepted, with offline taking effect.
OCX_TOOLCHAIN_ACTIVATE
How a rendered toolchain reaches your shell — by composing the environment at every prompt, by putting one directory on PATH, or not at all. Read by both tiers: a project's toolchain and the global one in $OCX_HOME.
export OCX_TOOLCHAIN_ACTIVATE=bin| Value | Behaviour |
|---|---|
env | Compose the toolchain environment on every prompt. The default when no tier sets the key. |
bin | Put the toolchain home's active/bin directory on PATH and compose nothing else — each tool is resolved by its launcher trampoline at the moment it runs. |
none | Neither. The reconciler withdraws whatever it owns and adds nothing. |
Parsed case-insensitively (Bin, BIN and bin are equivalent), unlike the ocx.toml key, which is case-sensitive lowercase. Whitespace is not trimmed: OCX_TOOLCHAIN_ACTIVATE=" bin" is an unrecognized value, not bin. An empty value reads as unset.
An unrecognized value warns and falls through — the tier is simply absent, resolution continues to the next one, and the command exits 0. The ocx.toml key does not forgive: an unrecognized value there is a parse error, exit 78. The asymmetry is deliberate. A typo in a file you committed should stop the build; a typo in a variable a CI image exported should not take every project on that runner down with it.
This variable is the weakest tier, not an override. It sits below ocx.toml's activate key, so a file that states a value — a project's own, or $OCX_HOME/ocx.toml for the global toolchain — wins over an exported one; the variable decides only where no file states the key.
ocx self setup --toolchain-activate MODE is not a fourth tier — it writes the key into $OCX_HOME/ocx.toml rather than overriding this variable for one invocation. That file is the global toolchain's own file tier and outranks this variable exactly as a project's does.
Not forwarded to child processes
Like OCX_LAZY_MODE, this variable changes how a toolchain reaches PATH, never which digest resolves, so it sits outside the forwarded set entirely. A child ocx reads its own environment.
OCX_TOOLCHAIN_DIR
The root directory under which each project's toolchain tree is rendered — the environment spelling of config.toml's toolchain-dir key. A project's tree lands at <root>/<project-key>/toolchain/, so this names one directory holding many projects, not one project's toolchain home. The global toolchain home ignores it entirely: that one is always $OCX_HOME/toolchain, whatever this resolves to.
export OCX_TOOLCHAIN_DIR=~/.cache/ocx/toolchainThis variable is the weakest tier, not an override. config.toml beats it, so a host that states the key wins over an exported value; the variable supplies continuity only where a child process cannot read that configuration for itself (OCX_NO_CONFIG=1, or a different --config). An empty value reads as absent at both tiers, so OCX_TOOLCHAIN_DIR="" does not erase a configured root.
Unlike the two toolchain settings on either side of it, this one is resolution-affecting — it moves where the links/<group>/<entry> links and the shells/default/bin trampolines live, so every composed path changes with it — and it is forwarded to child ocx processes. When the parent resolved no root, the forward removes any inherited value, so a stale export in your shell cannot outrank the outer ocx's parsed state.
Two expansion rules, and only two
A leading ~ expands against the home directory. Nothing else expands.
A %VAR% reference is taken literally on every platform, Windows included. So OCX_TOOLCHAIN_DIR='%LOCALAPPDATA%\ocx\toolchain' does not name a directory under AppData — it is a relative path whose first component is the literal text %LOCALAPPDATA%, and ocx refuses it at parse with exit 78 for being relative. Spell the directory out, or use ~.
Refusals
The resolved root is refused with exit 78 — naming the tier that declared it, so the message points at the file or the variable you can actually edit — when it:
- is relative, or carries a
..component; - resolves outside both the home directory and
$OCX_HOME; - is the home directory or
$OCX_HOMEitself rather than a directory beneath one; - is a filesystem root, or a system location such as
/usr,/etc,/var/lib,%SystemRoot%,C:\Program Files,C:\Program Files (x86)orC:\ProgramData; - sits at or under
$OCX_HOME/toolchain— a globalocx pullreconciles that directory as a whole and would prune other projects' trees there as orphan groups; - exists and is not a directory;
- on Linux and macOS only — is not owned by the invoking user, or grants write to group or world. The check runs against the root when it exists and against its nearest existing ancestor when it does not: that ancestor is the directory the renderer will create under, so it is the one whose permissions decide whether another account could plant a trampoline on your
PATH.
Windows checks no ownership. Reading a directory's owner there needs a Windows security API ocx does not yet call, so the last refusal simply does not fire on Windows. The directory-is-a-directory check does run everywhere.
The root does not have to exist. Resolution creates nothing — no directory, no probe file — and the tree is created when a toolchain is first rendered.
OCX_TOOLCHAIN_PINNED
Whether a composed toolchain environment names the digest roots ocx.lock pins, or the rendered links/<group>/<entry> links that point at them.
export OCX_TOOLCHAIN_PINNED=trueA truthy value selects the digest lane: composed paths name the exact packages the lock pinned at compose time, consulting no link and reading no rendered tree, so a later ocx update does not reach that environment. A falsy value selects the link lane, where an update moves a root's paths with no re-render. Following the links is the default.
Two qualifications ride the link lane either way. An entry whose link is absent, stale, or not a link composes on its digest path — that one entry, silently, while the rest of the composition still follows its links; the digest path is the correct path, the same package directory under its other spelling. Only roots have links: a dependency's PATH contributions and every ${deps.<name>.installPath} are digest paths in both lanes. See --pinned. An empty value reads as unset; an unparseable one warns and falls through, exit 0.
"Unset" and "explicitly false" are different answers here, deliberately. OCX_TOOLCHAIN_PINNED=false states the link lane and beats the ladder's floor; an unset variable states nothing and lets a lower tier answer.
This variable is the weakest tier, not an override. It sits below both --pinned / --no-pinned and ocx.toml's pinned key, so it decides only for an invocation where neither of those speaks. Three of the five composing emitters — ocx direnv export, the env-mode shell hook and the global login exporter — carry no flag, so for those this variable and the ocx.toml key are the whole ladder. See --pinned, --no-pinned for the full ladder, for which commands carry the flag, and for why ocx pull carries none.
OCX_UPDATE_CHECK_INTERVAL
Override the minimum interval between automatic update-check registry probes. The check runs once per shell invocation after the interval elapses; it is a background notification only and does not block the command.
| Value | Behaviour |
|---|---|
| Unset | Default 24-hour interval |
0 | Always check on every eligible invocation (bypass throttle) |
| Positive integer | Custom interval in seconds |
export OCX_UPDATE_CHECK_INTERVAL=3600 # check at most once per hour
export OCX_UPDATE_CHECK_INTERVAL=0 # always check (development use)The state file that tracks the last probe is at $OCX_HOME/state/update-check/ocx_sh_ocx_cli. Its mtime is the data — the file is zero bytes.
The automatic update check is also suppressed when stderr is not a terminal (typical CI runners, pipelines, and redirected output) and when CI is set. Setting OCX_UPDATE_CHECK_INTERVAL does not override these suppressions — the notification is intentionally invisible in non-interactive contexts.
To disable the check entirely rather than adjusting its frequency, use OCX_NO_UPDATE_CHECK. The explicit ocx self update and ocx self update --check commands always bypass this interval — they are explicit user intent.
Script env access (ocx package test --script)
ocx.env(name) in a Starlark test script reads one variable from the composed package env — the same env surface that ocx package exec exposes. It does not read the host process env.
No new OCX_* variable was introduced by the --script feature. The resolution-affecting variables listed above (OCX_BINARY_PIN, OCX_HOME, OCX_CONFIG, OCX_PROJECT, OCX_INDEX, and others) remain inaccessible to script ocx.env() calls and cannot be overridden via the ocx.run(env=...) overlay kwarg — those keys are reserved and any attempt to set them results in a Failed outcome.
ocx-mirror
The ocx-mirror tool lives in its own repository. Its environment variables are documented in the ocx-mirror environment reference.
External
CI
When set to a truthy value, OCX suppresses the update check on startup. Most CI systems (GitHub Actions, GitLab CI, Travis, etc.) set this automatically.
CI Integration Variables
ocx env --ci and ocx package env --ci read a small set of runner-provided variables to locate the CI system's persistence channel. These variables are read by OCX from the runner environment — they are not OCX configuration and carry no OCX_ prefix.
| Variable | Provider | Purpose |
|---|---|---|
GITHUB_ACTIONS | GitHub Actions | Provider auto-detection |
GITHUB_ENV | GitHub Actions | Sink for non-PATH entries |
GITHUB_PATH | GitHub Actions | Sink for PATH-type entries |
GITLAB_CI | GitLab CI/CD | Provider auto-detection |
These variables are not forwarded to child ocx processes
OCX's config-forwarding set propagates only OCX_* resolution-affecting variables to subprocesses (launchers, nested ocx exec). The runner variables above are not in that set — a child ocx invocation will not inherit them. This is intentional: the CI sink files are runner-managed paths and must not propagate through forked environments.
GITHUB_ACTIONS
Set to true by GitHub Actions runners. OCX reads this variable to auto-detect the CI provider when --ci is passed without an explicit provider value, and to suppress the update check on startup.
GITHUB_PATH
Set by GitHub Actions to a file path. Workflow steps append one directory per line to this file; each appended directory is prepended to PATH for all later steps. ocx env --ci=github and ocx package env --ci=github write only the literal PATH variable here — one directory per line, in prepend order — so OCX-installed tool directories land leftmost (highest priority) in PATH for every subsequent step, regardless of when the step runs.
All other path-type variables (such as LD_LIBRARY_PATH, MANPATH, PKG_CONFIG_PATH) are written to GITHUB_ENV as KEY=value, not to this file.
GITHUB_ENV
Set by GitHub Actions to a file path. Workflow steps append environment variables to this file using KEY=VALUE syntax (or heredoc delimiters for multiline values); the runner exports each entry to all later steps. ocx env --ci=github and ocx package env --ci=github write all non-PATH entries here: constant-type variables as KEY=VALUE, path-type variables other than PATH (such as LD_LIBRARY_PATH) as KEY=<prepended-value> with OCX-provided directories prepended to the existing value, and list-type variables (such as JDK_JAVA_OPTIONS) as KEY=<appended-value> with OCX-provided contributions appended to the existing value, joined by the key's separator.
GITLAB_CI
Set to true by GitLab CI/CD runners. OCX reads this variable to auto-detect the CI provider when --ci is passed without an explicit provider value.
DOCKER_CONFIG
The location of the Docker configuration directory. Read by the Docker credential helper that ocx delegates to when resolving registry credentials from ~/.docker/config.json.
OCX_AUTH_* environment variables (see above) take priority over any credential stored in the Docker configuration directory at read time. The resolution order for a given registry is: OCX_AUTH_TOKEN / OCX_AUTH_USER + OCX_AUTH_PASSWORD (highest) → credHelpers[registry] → credsStore → auths[registry] (lowest).
CA certificates — SSL_CERT_FILE, SSL_CERT_DIR
OCX verifies registry TLS against a compiled-in copy of the Mozilla CA root store, so HTTPS works out of the box even on a host with no certificates installed — a scratch or distroless container, a stripped CI image, an air-gapped runner. This is what makes the single static binary self-contained: it never depends on the system trust store being present.
On top of those built-in roots, OCX also loads the host's own trust store and merges the two sets — it never replaces the public roots. Any certificate the operating system already trusts, including a privately installed corporate root, is honored alongside the bundled ones. Both a public registry and an internal one fronted by a TLS-intercepting proxy work in the same invocation, with no verification disabled.
On Linux the host trust store is discovered through the platform's standard mechanism, overridable with two well-known variables read from the ambient environment (they are not OCX configuration and carry no OCX_ prefix):
| Variable | Meaning |
|---|---|
SSL_CERT_FILE | Path to a single file containing one or more certificates in PEM format. |
SSL_CERT_DIR | One or more directories (:-separated) of PEM certificate files. |
Point SSL_CERT_FILE at a bundle that includes your corporate root CA to pull from a proxy-fronted registry:
SSL_CERT_FILE=/etc/pki/corp-root.pem ocx package install internal/tool:1.2PEM text, not DER
These variables expect PEM armor (-----BEGIN CERTIFICATE-----), regardless of file extension — a .crt file is loaded only if its contents are PEM. A raw DER/binary certificate is ignored. Convert one with openssl x509 -inform der -in corp.crt -out corp.pem.
Disabling TLS for a local registry
To skip HTTPS entirely for a development registry that has no certificate, use OCX_INSECURE_REGISTRIES instead. It is scoped to the hosts you name and never weakens verification for public registries.
Proxies — HTTPS_PROXY, HTTP_PROXY, ALL_PROXY, NO_PROXY
On a network where outbound traffic is routed through a corporate HTTP proxy, OCX reads the same forward-proxy variables curl, git, and reqwest already read — there is no separate OCX_PROXY variable. This matters for more than routing: on many such networks OCX cannot resolve an external hostname itself at all, only the proxy can, so the SSRF guard that runs before every index-indirected registry dial (see trusted_hosts) adapts to a proxied destination instead of failing on a lookup only the proxy is able to make.
| Variable | Applies to |
|---|---|
HTTPS_PROXY | https:// destinations |
HTTP_PROXY | http:// destinations |
ALL_PROXY | Either scheme, when the scheme-specific variable above is unset |
NO_PROXY | Names destinations dialed directly, bypassing every variable above |
Each is checked upper-case first, then lower-case (https_proxy, http_proxy, all_proxy, no_proxy) as a fallback, and the whole set is read once at process start. An https:// destination consults HTTPS_PROXY, then falls back to ALL_PROXY — never HTTP_PROXY, whose scheme it does not share. NO_PROXY accepts an exact host, a leading-dot domain suffix, a CIDR block, or * to exclude every destination. On Windows and macOS, OCX also reads the operating system's own proxy configuration — the registry-based Internet Settings on Windows, the System Configuration framework on macOS — so a proxy set through Control Panel or System Settings applies with no environment variable at all.
Under a configured proxy, OCX never resolves the destination host itself — the proxy does, over a CONNECT tunnel for https:// or an absolute-form request line for http://. The SSRF guard adapts to this: for a proxied destination it skips the DNS pre-flight, since there is nothing local to resolve, but still refuses a forbidden IP literal — loopback, private, or link-local — written directly into an index root's pointer, and it admits the proxy's own hostname at the connection-time resolver hook, the one address the guard would otherwise have no way to approve. A destination NO_PROXY excludes is dialed directly instead, with the full resolve-then-validate guard unchanged.
Same variables curl and git already read
HTTPS_PROXY/HTTP_PROXY/ALL_PROXY/NO_PROXY are a de facto standard read by curl, git, and every HTTP client built on reqwest — including OCX's own. A proxy configured once, the same way, works for all of them.
XDG_CONFIG_HOME
User-level configuration base directory, defined by the XDG Base Directory Specification. On Linux, OCX uses it to locate the user-tier configuration file: the user tier is $XDG_CONFIG_HOME/ocx/config.toml, falling back to ~/.config/ocx/config.toml when the variable is unset.
On macOS, XDG_CONFIG_HOME is not consulted. The user-tier path is ~/Library/Application Support/ocx/config.toml, following Apple's directory conventions. Use the OCX home tier ($OCX_HOME/config.toml, default ~/.ocx/config.toml) if you want a platform-neutral user config path.
OCX does not write anything to these directories — the config loader is read-only. This follows the convention for CLI tools that need user-level configuration separate from data (~/.ocx/).
NO_COLOR
When set to any non-empty value, disables ANSI color output. This is a cross-tool convention for respecting user color preferences. The --color flag takes precedence.
CLICOLOR
When set to 0, disables color output. Part of the CLICOLOR convention.
CLICOLOR_FORCE
When set to a non-zero value, forces color output even when stdout is not a terminal. Overrides CLICOLOR but is itself overridden by NO_COLOR.
RUST_LOG
A fallback for configuring the log level of OCX and its dependencies. If OCX_LOG is not set, OCX will respect the log level configured via RUST_LOG. The format for this variable is the same as for OCX_LOG.