Skip to content

Environment variables

There are two distinct categories, and conflating them causes most of the confusion about what this crate reads.

CI is the only variable the crate reads on its own

Variable Read by Effect
CI Resolver::resolve and Resolver::probe When exactly true, a literal credential is refused

Nothing else is consulted without a tool naming it.

The comparison is exact and case-sensitive

std::env::var("CI").as_deref() == Ok("true")

Only the lowercase string true triggers refusal:

Value of CI Literal refused?
true yes
1 no
TRUE no
True no
yes no
unset no

A pipeline that sets CI=1 gets no protection from this check. If you rely on the refusal, confirm the value your platform actually sets, or set CI=true yourself in the job.

Which platforms set it

The crate's own documentation names GitHub Actions, GitLab CI, CircleCI and Buildkite as platforms following the CI=true convention.

Others vary — some set a different value, some set nothing and expose only a product-specific variable such as JENKINS_URL. The crate deliberately does not detect those: matching CI_* prefixes or vendor-specific names produces false positives in developer shells that export similar variables for unrelated reasons.

Setting CI=true explicitly in a job is the reliable way to get the behaviour on any platform.

Turning the refusal off locally

A developer shell that exports CI=true for some other tool will find literal credentials refused. Any value other than true restores them — CI=false is the value the crate's own error help suggests, and unsetting the variable works equally well.

CI=false mytool run

Variables named by configuration

The env and fallback_env keys of a CredentialRef each hold the name of a variable to read. These are chosen by the tool and its operator, not by this crate.

fallback_env is conventionally an ecosystem-wide name that the tool did not invent:

Example Belongs to
ANTHROPIC_API_KEY Anthropic tooling generally
GITHUB_TOKEN GitHub tooling generally

These are examples of the convention, not defaults. The crate has no built-in list and no implicit lookup: a credential with no env and no fallback_env is never resolved from the environment, whatever variables happen to be set.

EnvStore reads the variable named by its account argument

Used directly rather than through a Resolver, EnvStore takes the variable name as its account parameter and ignores service entirely:

store.get("ignored", "MYTOOL_TOKEN").await?;  // reads $MYTOOL_TOKEN

A variable that is set but empty counts as found

std::env::var returns Ok("") for a variable set to an empty value, and neither the resolver nor EnvStore checks for it.

export MYTOOL_TOKEN=""

With that exported, a reference naming MYTOOL_TOKEN in env resolves successfully to an empty SecretString. The chain stops there: the keychain, literal and fallback legs are never tried, because the first leg was a hit.

The failure surfaces later, wherever the empty secret is used — usually as a 401 from an API rather than as a credential error. An exported but empty variable shadows every lower-precedence source, which makes export MYTOOL_TOKEN= a surprisingly effective way to break a working configuration.