Skip to content

Supply credentials in CI

Use an environment variable. The keychain is unavailable on most runners, and literals are refused outright.

Reference a variable from config

anthropic:
  env: MYTOOL_ANTHROPIC_KEY

Then inject the secret as that variable in the job. In GitLab CI, a masked project variable of the same name is already in the environment:

run-tool:
  script:
    - mytool run
  variables:
    MYTOOL_ANTHROPIC_KEY: $ANTHROPIC_KEY_SECRET

In GitHub Actions:

- run: mytool run
  env:
    MYTOOL_ANTHROPIC_KEY: ${{ secrets.ANTHROPIC_KEY }}

Remove literals from any config that runs in CI

A literal key plus CI=true fails with LiteralRefusedInCi.

The failure is not softened by having other sources configured. When a literal is present and refused, resolution stops — it does not continue to fallback_env, even when that variable is set:

# fails in CI despite the fallback being available
anthropic:
  literal: sk-ant-...
  fallback_env: ANTHROPIC_API_KEY

Delete the literal key. There is no flag that makes the fallback take over.

Confirm your platform sets CI to the value the check expects

The refusal triggers only on the exact lowercase string true. CI=1 and CI=TRUE do not trigger it, so on those platforms a literal secret will be read and used in the pipeline.

If you are relying on the refusal, set the variable explicitly:

variables:
  CI: "true"

Do not configure a keychain for a CI job

CI runners rarely have a working keychain, and a keychain backend error aborts resolution rather than falling through to the legs below it. A config carrying both a keychain and a fallback_env key can therefore fail in CI even though the fallback variable is set, because the keychain leg is tried second and never returns a clean "not found". Only the ref's own env key escapes this, being the leg tried first.

Keep CI configuration to env alone.

Turn the refusal off for a local run

A developer shell that exports CI=true for something else will find literals refused. Any other value restores them:

CI=false mytool run

Check what a pipeline will do before running it

Resolver::probe reports which source would win without printing the secret, which makes it safe to run in a job log:

use rtb_credentials::{ResolutionOutcome, Resolver};

match resolver.probe(&config.anthropic).await? {
    ResolutionOutcome::Resolved(source) => println!("resolves via {source:?}"),
    ResolutionOutcome::LiteralRefusedInCi => println!("literal refused under CI"),
    ResolutionOutcome::Missing => println!("nothing configured resolves"),
    _ => println!("unrecognised outcome"),
}

ResolutionOutcome is #[non_exhaustive], so the wildcard arm is required.